Java 多线程基础知识

2020.12.15 16:00

多线程的使用

对于 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 实例,也就是默认是执行 targetrun() 方法。如果继承 Thread 并重写了 run() 方法,自然也就不会执行 targetrun() 方法了。

其实本质上所有实现多线程的方法都是实例化 Thread 并调用 start 方法,start 方法会调用一个 native 方法,来启动线程并调用 Threadrun 方法。

实现 Runnable 更好还是继承 Thread 更好?

实现 Runnable 更好,主要原因是:

线程的状态

Java 的线程,也就是 Thread 类,有 6 种状态:

  1. New:已创建但还尚未启动

  2. Runnable:可运行等待调度或者运行中

  3. Blocked:被阻塞

  4. Waiting:等待

  5. Timed Waiting:限期等待

  6. Terminated:终止

Java 线程的状态转移图:

状态转移图

线程的主要属性