我们先来看一下平时所用的几种创建线程的方式

继承Thread类

  1. package ltd.personalstudy.mythread;
  2. import java.io.IOException;
  3. public class CreateThread extends Thread {
  4. public static void main(String[] args) {
  5. CreateThread createThread = new CreateThread();
  6. createThread.start();
  7. }
  8. @Override
  9. public void run() {
  10. System.out.println("打印一句话");
  11. }
  12. }
  • 继承Thread类
  • 重写run方法
  • 调用Thread类的start方法即可启动一个线程

    实现Runnable接口

    ```java package ltd.personalstudy.mythread;

/**

  • @Author 咖啡杯里的茶
  • @date 2020/8/2 */ public class CreateThreadTwo implements Runnable { @Override public void run() {

    1. System.out.println("打印一句话");

    }

    public static void main(String[] args) {

    1. CreateThreadTwo createThreadTwo = new CreateThreadTwo();
    2. Thread thread = new Thread(createThreadTwo);
    3. thread.start();

    } } ```

    使用线程池

    ```java package ltd.personalstudy.mythread;

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /**

  • @Author 咖啡杯里的茶
  • @date 2020/8/2 */ public class MyExecutor { public static void main(String[] args) {

    1. ExecutorService executorService = Executors.newFixedThreadPool(5);
    2. MyRunnable myRunnable = new MyRunnable();
    3. for (int i = 0; i < 20; i++) {
    4. Future<?> submit = executorService.submit(myRunnable);
    5. }
    6. // 回收线程,如果不回收,线程会一直在
    7. //executorService.shutdown();

    }

    static class MyRunnable implements Runnable {

    1. @Override
    2. public void run() {
    3. System.out.println("打印数据");
    4. }

    } } `` 使用线程池执行完任务之后并不会自动回收线程,看下图,当任务执行完成之后,程序依然在运行,除非是我们手动调用shutdown`方法,就会停止线程。
    image.png

等等,还有一些其他的创建线程的方式。

线程的本质

对于前面两种比较好理解,无论是继承Thread类还是实现Runnable接口,我们都是要创建Thread类,然后调用Thread类里面的start方法来启动一个线程,而这个线程执行的任务则是run方法里面的内容,无论是使用哪种方式。

  • 如果是继承Thread类的方式,则是执行重写了Thread类的run方法
  • 如果是实现了Runnable接口的方式,则是执行该接口的run方法。
    • 其实这个实现还是先调用的Thread类里面的run方法,我们来看一下Thread类中的run方法
      image.png

可以看到在Thread类中,当我们调用run方法的时候,实际上调用的是targetrun方法,而target就是我们传进来的实现了Runnable接口的类。

从上面两个简单的创建线程执行任务的方式可以看到,其实线程分为两个部分。

  • 启动线程
  • 线程执行任务

image.png
就像上图描述的一样,即使我们不是继承Thread类,而是实现了Runnable接口,但是还是离不开Thread类,最终还是要调用Thread类的start方法才算是启动了一个线程,而无论是哪种方式都会发现我们最终执行的任务都是run方法。

线程池

上面我们是从继承Thread类和实现Runnable接口来得出的结论,但是可以看到使用线程池来创建线程的时候我们是没有看到Thread类的影子的。先再看一下我们线程池的例子。
image.png
使用线程池有两个关键的代码

  • 初始化线程池
  • 将任务提交给线程

    初始化线程池

    image.png
    我们看主线,会发现在初始化线程池的过程中,实际上是创建了DefaultThreadFactory对象。这个对象里面有一个newThread方法,这个方法返回的就是Thread类,不过这个方法是在哪里调用的呢,这就需要看将任务提交给线程的那部分的代码了。

将任务提交给线程执行

这里我们就需要从submit方法开始往下查看了
image.png
从上图可以知道,就算是使用线程池的方式创建线程,其实还是需要使用Thread类,从上面的图我们已经看到了,在java.util.concurrent.ThreadPoolExecutor类的addWorker方法中会获取到Thread对象,最终还是要通过Thread对象的start方法来启动一个线程。有兴趣可以继续看addWorker方法,在后面是会调用start方法的。

所以得到的结论是 “创建线程的方式本质上只有一个,就是使用Thread类的start方法来创建”。

任务类型

通过上面的讲解我们已经知道,想要使用线程实际上是分为两步

  • 创建一个线程,而创建线程的方式本质上是只有一种,那就是使用Thread类来创建,通过start方法来启动。
  • 将我们的任务提交给线程来执行。

第二步中就是将我们的任务提交给线程来执行,那么我们有几种方式来创建任务呢?答案是两种。

  • 一种是没有返回值的任务,就是我们上面讲解的实现Runnable接口就是这种方式
  • 第二种方式就是有返回值的任务,使用的是实现Callable接口,会返回一个Future对象,里面就包含了返回值。