有多少种实现线程的方法?

  1. 从不同的角度看,会有不同的答案。
  2. 典型答案是两种,分别是实现 Runnable 接口和继承 Thread 类,然后具体展开说;
  3. 但是,我们看原理,其实Thread类实现了 Runnable 接口,并且看 Thread 类的 run 方法,会发现其实那两种本质都是一样的,run 方法的代码如下:
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

方法一和方法二,也就是 “继承Thread类然后重写 run()” 和 “实现 Runnable 接口并传入 Thread 类” 在实现多线程的本质上,并没有区别,都是最终调用了 start() 方法来新建线程。这两个方法的最主要区别在于 run() 方法的内容来源:
方法一:最终调用 target.run();
方法二:run() 整个都被重写

  1. 然后具体展开说其他方式;还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没有逃出过本质,也就是实现 Runnable 接口和继承 Thread 类。
  2. 结论:我们只能通过新建 Thread 类这一种方式来创建线程,但是类里面的 run 方法有两种方式来实现,第一种是重写 run 方法,第二种实现 Runnable 接口的 run 方法,然后再把该 runnable 实例传给 Thread 类。除此之外,从表面上看线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。

以上这种描述比直接回答一种、两种、多种都更准确。

实现 Runnable 接口相比于继承 Thread 类的优点:

  1. 从代码架构角度: 具体的任务 (run 方法) 应该和 "创建和运行的机制 (Thread 类) " 解耦, 用 Runnable 对象可以实现解耦.
  2. 实现继承 Thread 的方式的话, 那么每次想新建一个任务, 只能新建一个独立的线程, 而这样做的损耗会比较大(比如从头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是 run() 函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用 Runnable 和线程池, 可以反复利用同一个线程, 就可以大大减小这样的损耗.
  3. 继承 Thread 类以后,由于 Java 语言不支持双继承, 这样就无法再继承其他的类, 限制了可扩展性.

因此通常我们优先选择 Runnable 接口.

FutureTask、线程池以及定时器属于多线程的实现方式吗?

多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。

以上的观点之所以错误,是因为他们都只不过是包装了 new Thread(),我们如果把能新建线程的类都称作是一种实现线程的方法,那么就太流于表面了,而没有理解到底层的原理。

而随着 JDK 的发展,这样的类会越来越多,我们肯定也没办法熟悉每一种具有新建线程能力的类,因为有些类根本不常用。

线程池

以下是使用线程池实现线程的代码

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Task() {
            });
        }
    }
}

class Task implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}
public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }

FutureTask

以下是使用 FutureTask 类实现线程的代码

public class FutureTaskDemo {
    public static class CallerTask implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello";
        }
    }

    public static void main(String[] args) {
        // 创建异步任务
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        // 启动线程, 调用 futureTask 中 run()
        new Thread(futureTask).start();
        try {
            String result = futureTask.get();
            System.out.println(result);
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

从源码中可以看到 FutureTask 类实现了 RunnableFuture 接口

public class FutureTask<V> implements RunnableFuture<V> {

而 RunnableFuture 接口继承了 Runnable 接口

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

其中, futureTask 实例实现了 Runnable 接口, 并传入 Thread 的构造函数中, 也就是说在执行 start() 的时候会调用 futureTask 中的 run() .

        // 创建异步任务
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        // 启动线程
        new Thread(futureTask).start();

以下是 FutureTask 的部分构造函数和 run() 源码

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

    ...

    public void run() {
        if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        ...

我们自己实现的 CallerTask 类作为 Callable 对象传入 FutureTask 构造函数中, 紧接着在 run() 中会去调用传进来的 callable 对象的 call(), 也就是我们自己实现的 call(), 并将结果使用 set() 存到 outcome 中.

简而言之, 使用 FutureTask 实现线程的时候本质上还是重写了 Runnable 的 run(), 不过区别在于 FutureTask 可以拿到任务的返回结果, 而继承 Thread 类和实现 Runnable 接口都无法拿到返回结果.

start方法的执行流程是什么?

  1. 检查线程状态, 只有 NEW 状态下的线程才能继续, 否则会抛出 IllegalThreadStateException (在运行中或者已结束的线程,都不能再次启动,详见CantStartTwice10 类)
  2. 被加入线程组
  3. 调用 start0() 方法启动线程

注意点:
start 方法是被 synchronized 修饰的方法, 可以保证线程安全;

由 JVM 创建的 main 方法线程和 system 组线程, 并不会通过 start 来启动.

为何调用 start(), 而不是直接调用 run() 呢?

调用 start() 之后, 一个线程才会被加入线程组, 进入生命周期. 如果仅仅从 main 中直接调用 run(), 那只是从 main 线程执行了一次 run() 方法, 并无新线程产生.

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

Live in the future, then build what's missing.