创建线程的几种方式(三)

267 阅读2分钟

使用线程池线程

下面我们看一下第三种方式,即使用线程池来创建线程

  private void createThread7(){
    Callable<Integer> callable = () -> new Random().nextInt();
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    Future<Integer> future = executorService.submit(callable);
    try {
      System.out.println(future.get());
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }
    executorService.shutdown();
  }

上面使用Excutors类创建了单线程池,该类还有多种其他方式来创建线程池,查看这些类的源代码,你会发现它们都需要一个ThreadFactory参数,它是个接口,进一步查看它的源代码如下:

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

只有一个方法返回Thread对象,也就是说线程池的创建过程本质也是通过第二种方式来创建线程

使用Timer线程

第四种方式是通过Timer来创建线程.Timer在执行定时任务时确会启动新的线程,但是查看Timer的源代码会发现它还是继承了Thread来重写了run方法,所以说本质上它使用第一种方式来创建线程.下面的示例将启动一个Timer线程,运行4秒后结束

  private void createThread8(){
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
      @Override
      public void run() {
        System.out.println("hello");
      }
    },0,2000);
    try {
      Thread.sleep(4000);
      timer.cancel();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

总结

总的来说,我们使用了四种方式创建线程:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用线程池
  4. 使用Timer 前两种是JDK文档明确提到的,后两种本质上也是使用前两种来创建的线程,所以归根到底还是两种方式.只不过为了适应不同场景发明了其他方式.

对比前两种方式,一般来说我们更愿意使用第二种,首先是因为Runnable方式把run方法的具体实现跟线程本身操作分开,让开发人员更专注于业务相关的操作,相当于策略模式的实现.这是一种解耦,一旦解耦,那么势必降低维护成本.另一方面是因为java语言是单继承的,一旦继承了Thread类,就无法继承其他类,这在很多场景下也是无法接受的.

在网上还看到说创建线程本质上只有一种方式,就是实例化Thread类,然后启动该类.具体该Thread类如何工作,你可以通过继承Thread类重写run方法来实现,也可以通过实现Runnable接口的run方法来实现,这也是解释的过去的.所以当面试官问你有多少种创建方式时,不管你回答一种,两种,还是四种,关键是要给出一个合理的解释.