美文网首页Java架构技术进阶Java老男孩的成长之路
Java并发编程—刨根问底搞懂创建线程到底有几种方法?

Java并发编程—刨根问底搞懂创建线程到底有几种方法?

作者: java菲菲 | 来源:发表于2020-02-24 21:08 被阅读0次

    推荐阅读

    1. 2020复工跳槽季,ZooKeeper灵魂27连问,这谁顶得住?
    2. 疫情期间“闭关修炼”,吃透这本“Java核心知识”,跳槽面试不心慌
    3. 秋招JAVA面试总结:Java+并发+Spring+MySQL+分布式+Redis+算法+JVM等
    4. 终极手撕之架构大全:分布式+框架+微服务+性能优化,够不够?

    Oracle官方文档对创建线程的说明

    • Java SE 8 API文档: docs.oracle.com/javase/8/do…

      请查看java.lang.Thread的类说明文档。

      1. 将类声明为Thread的子类,并重写run方法。

      官方原话:There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread.

      /**
       * 实现线程的第一个方式 继承Thread
       * @author yiren
       */
      public class MyThread extends Thread {
      
          @Override
          public void run() {
              System.out.println(Thread.currentThread().getName() + " Thread running...");
          }
      
          public static void main(String[] args) throws IOException {
              new MyThread().start();
              System.in.read();
          }
      }
      
      
      1. 实现Runnable接口,并实现run方法。

      官方原话:The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method.

    /**
     * 实现线程的第二个方式 实现Runnable接口
     * @author yiren
     */
    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " Runnable running...");
        }
    
        public static void main(String[] args) throws IOException {
            new Thread(new MyRunnable()).start();
            System.in.read();
        }
    }
    

    两种发方法的优缺点

      1. 从代码的结构上去考虑,具体线程执行的任务是run方法中的代码,相当于业务代码,它应该和我们线程的创建解耦。
      2. 如果继承Thread,每次想建一个异步任务,我们都需要去建立一个独立的线程。而创建一个线程的消耗是比较大的,如果是使用Runnable,我们就可以很好得利用线程池之类的工具,这样可以大大减少创建和销毁线程的损耗。节省资源。
      3. 由于Java是单继承,继承了Thread的类过后,该类就无法再继承其他类,大大限制了可扩展性。
        /* What will be run. */
        private Runnable target;
    
            public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    
            private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize) {
            init(g, target, name, stackSize, null, true);
        }
    
            private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
            ......
    
            this.target = target;
    
            ......
        }
    
            @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    
    • 两种方法的本质(由上面的Thread类的源代码可知)

      • 继承Thread 是整个run()方法被重写
      • 实现Runnable最终是Runnablerun方法被target.run()调用
    • 如果两种方式都用会有什么效果呢?

      /**
       * @author yiren
       */
      public class MyThreadAndRunnable {
          public static void main(String[] args) {
              Thread thread = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println(Thread.currentThread().getName() + " runnable running...");
                  }
              }
              ) {
                  @Override
                  public void run() {
                      System.out.println(Thread.currentThread().getName() + " thread running...");
                  }
              };
      
              // 这个地方应该是执行重写Thread类的run方法中的逻辑!
              thread.start();
          }
      }
      
      • 很明显,上面说了不重写Threadrun()方法就是调用target.run(),如果重写那也就没有调用target.run()了。
    • 准确来说,创建线程只有一种方式,那就是构建Thread类,而实现线程的执行单元有两种方法,就是上面说的两种。

    其他说法:

    • 线程池创建线程

      /**
       * wrong 线程池创建
       *
       * @author yiren
       */
      public class ThreadPoolExample {
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newCachedThreadPool();
              for (int i = 0; i < 1000; i++) {
                  executorService.submit(() -> {
                      try {
                          Thread.sleep(500);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println(Thread.currentThread().getName());
                  });
              }
          }
      }
      
      // Executors中的DefaultThreadFactory 
      static class DefaultThreadFactory implements ThreadFactory {
              private static final AtomicInteger poolNumber = new AtomicInteger(1);
              private final ThreadGroup group;
              private final AtomicInteger threadNumber = new AtomicInteger(1);
              private final String namePrefix;
      
              DefaultThreadFactory() {
                  SecurityManager s = System.getSecurityManager();
                  group = (s != null) ? s.getThreadGroup() :
                                        Thread.currentThread().getThreadGroup();
                  namePrefix = "pool-" +
                                poolNumber.getAndIncrement() +
                               "-thread-";
              }
      
              public Thread newThread(Runnable r) {
                  Thread t = new Thread(group, r,
                                        namePrefix + threadNumber.getAndIncrement(),
                                        0);
                  if (t.isDaemon())
                      t.setDaemon(false);
                  if (t.getPriority() != Thread.NORM_PRIORITY)
                      t.setPriority(Thread.NORM_PRIORITY);
                  return t;
              }
          }
      
      • 由上newThread()方法可知,即使是线程池,本质上还是使用Thread的创建线程。
    • Callable和FutureTask创建线程


      image.png
    image.png
    • 由上UML图可知,实际上内部还是使用的Thread和Runnable来实现的。

    • 定时器Timer

      
      /**
       * @author yiren
       */
      public class TimerExample {
          public static void main(String[] args) {
              Timer timer = new Timer();
              // 每隔1s打印下自己的名字
              timer.scheduleAtFixedRate(new TimerTask() {
                  @Override
                  public void run() {
                      System.out.println(Thread.currentThread().getName() + " timer running...");
                  }
              }, 1000, 1000);
          }
      }
      
      • 它的TimerTask其实也是实现了Runnable接口,可以看下TimerTask这个抽象类
    • 匿名内部类、Lambda表达式 (本质也是一样的)

      /**
       * 匿名内部类
       * @author yiren
       */
      public class AnonymousInnerClassExample {
          public static void main(String[] args) {
              new Thread() {
                  @Override
                  public void run() {
                      System.out.println(Thread.currentThread().getName());
                  }
              }.start();
      
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println(Thread.currentThread().getName());
                  }
              }).start();
      
          }
      }
      
      /**
       * lambda表达式
       * @author yiren
       */
      public class LambdaExample {
          public static void main(String[] args) {
              new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
          }
      }
      

    作者:苡仁
    原文链接:https://juejin.im/post/5e3f9669e51d4526cb160ee9

    相关文章

      网友评论

        本文标题:Java并发编程—刨根问底搞懂创建线程到底有几种方法?

        本文链接:https://www.haomeiwen.com/subject/ituwqhtx.html