Java 多线程基础知识
多线程的使用
对于 Java 使用多线程有几种方式,很多书或者网上有很多文章都有不同的结论,2 种、3 种、甚至 4 种都有,但是严格来讲应该是 2 种,这是来自 Java 官方文档的结论:
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. ...... The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started.
翻译过来简要说就是有两种方式可以启动一个新线程,一种是继承 Thread
类,一种是实现 Runnable
接口。
继承 Thread 类
继承 Thread
类需要实现 run()
方法,当调用 start()
方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run()
方法。
public class MyThread extends Thread {
public void run() {
// do something...
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
实现 Runnable 接口
需要实现接口中的 run()
方法,然后将实现类的实例作为参数创建一个 Thread
实例。
public class MyRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
实现 callable 接口
上面两种方式都不能获取到返回值,而 Java 并发工具包为我们提供了一种可以获取线程返回值的方式,也就是实现 callable
接口。实现 callable
接口需要实现 call()
方法,然后使用 FutureTask
封装。
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
为什么 Java 文档写的是有两种使用线程的方式,而这里却还多了一种方式?实际上 FutureTask 还是实现了 Runnable 接口,所以本质上就是实现 Runnable 接口。FutureTask 的重点在于 run() 方法调用了 Callable 实例的 call() 方法,并把结果保存了起来,而主线程调用 get() 方法时,如果还没有拿到计算结果则会使用 LockSupport 阻塞主线程,而拿到结果后,再使用 LockSupport 取消阻塞主线程。
后面再写一篇文章详细分析 FutureTask。
另外,其实 Thread 也实现了 Runnable 接口,可以看看 Thread 的 run()
方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
注意这个 target
就是构造方法传进来的 Runnable
实例,也就是默认是执行 target
的 run()
方法。如果继承 Thread
并重写了 run()
方法,自然也就不会执行 target
的 run()
方法了。
其实本质上所有实现多线程的方法都是实例化 Thread
并调用 start
方法,start
方法会调用一个 native
方法,来启动线程并调用 Thread
的 run
方法。
实现 Runnable 更好还是继承 Thread 更好?
实现 Runnable
更好,主要原因是:
应该将“任务类”和“创建与运行多线程的类”解耦,让任务类实现 Runnable
接口,可以传给不同的 Thread
,而任务类无需负责创建和运行线程。
继承 Thread
的话,每次想新建一个任务都必须新建一个线程,这个操作的损耗是比较大的,实现 Runnable
并使用线程池多次执行任务可以大大减少这种损耗。
Java 的普通类不支持多继承,继承了 Thread
类就不能继承其他类了,限制了其拓展性。
线程的状态
Java 的线程,也就是 Thread 类,有 6 种状态:
New
:已创建但还尚未启动
Runnable
:可运行等待调度或者运行中
Blocked
:被阻塞
Waiting
:等待
Timed Waiting
:限期等待
Terminated
:终止
Java 线程的状态转移图:
线程的主要属性