继承Thread类
package ltd.personalstudy.mythread;
import java.io.IOException;
public class CreateThread extends Thread {
public static void main(String[] args) {
CreateThread createThread = new CreateThread();
createThread.start();
}
@Override
public void run() {
System.out.println("打印一句话");
}
}
/**
- @Author 咖啡杯里的茶
@date 2020/8/2 */ public class CreateThreadTwo implements Runnable { @Override public void run() {
System.out.println("打印一句话");
}
public static void main(String[] args) {
CreateThreadTwo createThreadTwo = new CreateThreadTwo();
Thread thread = new Thread(createThreadTwo);
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) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
MyRunnable myRunnable = new MyRunnable();
for (int i = 0; i < 20; i++) {
Future<?> submit = executorService.submit(myRunnable);
}
// 回收线程,如果不回收,线程会一直在
//executorService.shutdown();
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("打印数据");
}
} }
`` 使用线程池执行完任务之后并不会自动回收线程,看下图,当任务执行完成之后,程序依然在运行,除非是我们手动调用
shutdown`方法,就会停止线程。
等等,还有一些其他的创建线程的方式。
线程的本质
对于前面两种比较好理解,无论是继承Thread
类还是实现Runnable
接口,我们都是要创建Thread
类,然后调用Thread
类里面的start
方法来启动一个线程,而这个线程执行的任务则是run
方法里面的内容,无论是使用哪种方式。
- 如果是继承
Thread
类的方式,则是执行重写了Thread
类的run
方法 - 如果是实现了
Runnable
接口的方式,则是执行该接口的run
方法。- 其实这个实现还是先调用的Thread类里面的
run
方法,我们来看一下Thread
类中的run
方法
- 其实这个实现还是先调用的Thread类里面的
可以看到在Thread
类中,当我们调用run
方法的时候,实际上调用的是target
的run
方法,而target
就是我们传进来的实现了Runnable
接口的类。
从上面两个简单的创建线程执行任务的方式可以看到,其实线程分为两个部分。
- 启动线程
- 线程执行任务
就像上图描述的一样,即使我们不是继承Thread
类,而是实现了Runnable
接口,但是还是离不开Thread
类,最终还是要调用Thread
类的start
方法才算是启动了一个线程,而无论是哪种方式都会发现我们最终执行的任务都是run
方法。
线程池
上面我们是从继承Thread
类和实现Runnable
接口来得出的结论,但是可以看到使用线程池来创建线程的时候我们是没有看到Thread
类的影子的。先再看一下我们线程池的例子。
使用线程池有两个关键的代码
- 初始化线程池
- 将任务提交给线程
初始化线程池
我们看主线,会发现在初始化线程池的过程中,实际上是创建了DefaultThreadFactory
对象。这个对象里面有一个newThread
方法,这个方法返回的就是Thread
类,不过这个方法是在哪里调用的呢,这就需要看将任务提交给线程的那部分的代码了。
将任务提交给线程执行
这里我们就需要从submit
方法开始往下查看了
从上图可以知道,就算是使用线程池的方式创建线程,其实还是需要使用Thread
类,从上面的图我们已经看到了,在java.util.concurrent.ThreadPoolExecutor
类的addWorker
方法中会获取到Thread
对象,最终还是要通过Thread
对象的start
方法来启动一个线程。有兴趣可以继续看addWorker
方法,在后面是会调用start
方法的。
所以得到的结论是 “创建线程的方式本质上只有一个,就是使用Thread类的start方法来创建”。
任务类型
通过上面的讲解我们已经知道,想要使用线程实际上是分为两步
- 创建一个线程,而创建线程的方式本质上是只有一种,那就是使用
Thread
类来创建,通过start
方法来启动。 - 将我们的任务提交给线程来执行。
第二步中就是将我们的任务提交给线程来执行,那么我们有几种方式来创建任务呢?答案是两种。
- 一种是没有返回值的任务,就是我们上面讲解的实现
Runnable
接口就是这种方式 - 第二种方式就是有返回值的任务,使用的是实现
Callable
接口,会返回一个Future
对象,里面就包含了返回值。