JUC 创建线程的四种方式与分析

289 阅读3分钟

创建线程是并发编程中基础中的基础,在这之后才可以进行并发编程的内容。

总体大纲

image.png

第一种:实现 Runnable 接口

public class Demo1 implements Runnable {

    @Override
    public void run() {
        System.out.println("用 Runnable 接口创建线程");
    }

    public static void main(String[] args) {
        // 启动一个线程
        new Thread(new Demo1()).start();
    }
}

Runnable 源码为:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

因为 Runnable 是函数式接口,所以可以使用 Lambda 表达式简化

// 启动一个线程
new Thread(() -> {
    System.out.println("用 Runnable 接口创建线程 | Lambda 表达式");
}).start();

第二种:继承 Thread 类

public class Demo2 extends Thread {

    @Override
    public void run() {
        System.out.println("继承 Thread 类创建线程");
    }


    public static void main(String[] args) {
        // 启动一个线程
        new Demo2().start();
    }
}

第三种:实现 Callable 接口

这种创建线程的方式是有返回值的

public class Demo3 implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("实现 Callable 接口创建线程,有返回值");

        return "hello Callable";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 这个 FutureTask 就相当于 Runnable 和 Callable 的一个中间人
        FutureTask<String> futureTask = new FutureTask<>(new Demo3());
        // 创建一个线程
        new Thread(futureTask).start();
        // 获取返回值
        System.out.println("Callable 接口的返回值为:" + futureTask.get());
    }
}

Callable 源码为:

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

因为 Runnable 是函数式接口,所以可以使用 Lambda 表达式简化

// 这个 FutureTask 就相当于 Runnable 和 Callable 的一个中间人
FutureTask<String> futureTask1 = new FutureTask<>(() -> {
    System.out.println("实现 Callable 接口创建线程,有返回值 | Lambda 表达式");

    return "hello Callable";
});
// 创建一个线程
new Thread(futureTask1).start();
// 获取返回值
System.out.println("Callable 接口的返回值为:" + futureTask.get());

第四种:使用线程池

public class Demo4 {

    public static void main(String[] args) {
        // 创建一个线程池
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                // 核心线程数
                Runtime.getRuntime().availableProcessors(),
                // 最大线程数
                Runtime.getRuntime().availableProcessors() * 2 + 1,
                // 存活时间
                5,
                // 时间单位
                TimeUnit.SECONDS,
                // 阻塞队列
                new ArrayBlockingQueue<>(10),
                // 创建线程的工厂
                Executors.defaultThreadFactory(),
                // 拒绝策略
                new ThreadPoolExecutor.AbortPolicy());
        // 创建一个线程
        poolExecutor.execute(() -> {
            System.out.println("线程池创建线程");
        });
    }
}

其实这里真正创建线程的核心是线程工厂

Executors.defaultThreadFactory() 其实就是 new DefaultThreadFactory()

DefaultThreadFactory 类源码为:

static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    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;
    }
}

通过 newThread() 这个方法我们可以发现,其实线程池创建线程还是 new 了一个 Thread 类。

实现 Runnable 接口比继承 Thread 类实现线程要好

(1)代码的架构考虑。实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 RunnableThread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。

(2)在某些情况下可以提高性能。使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走到生命周期的尽头被销毁,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类。如果我们使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。

(3)Java 不支持多继承。如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

综上所述,我们应该优先选择通过实现 Runnable 接口的方式来创建线程。