# 创建线程有哪些方式

Java 可以用四种方式来创建线程,如下所示:

  1. 继承 Thread 类创建线程

  2. 实现 Runnable 接口创建线程

  3. 使用 Callable 和 Future 创建线程

  4. 使用线程池例如用 Executor 框架继承 Thread 类创建线程

下面让我们分别来看看这四种创建线程的方法。

  1. 通过继承 Thread 类来创建并启动多线程的一般步骤如下

    • 定义 Thread 类的子类,并重写该类的 run () 方法,该方法的方法体就是线程需要完成的任务,run () 方法也称为线程执行体。

    • 创建 Thread 子类的实例,也就是创建了线程对象

    • 启动线程,即调用线程的 start () 方法

public class MyThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        // 做一些操作
    }
}
  1. 通过实现 Runnable 接口创建并启动线程一般步骤如下:

    • 定义 Runnable 接口的实现类,一样要重写 run () 方法,这个 run()方法和 Thread 中的 run () 方法一样是线程的执行体

    • 创建 Runnable 实现类的实例,并用这个实例作为 Thread 的 target 来创建 Thread 对象,这个 Thread 对象才是真正的线程对象

    • 第三部依然是通过调用线程对象的 start () 方法来启动线程

public class MyThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(new MyRunnable());
        myThread.start();
    }
}
class MyRunnable  implements Runnable {
    @Override
    public void run() {
        // 做一些操作
    }
}
  1. 和 Runnable 接口不一样,Callable 接口提供了一个 call()方法作为线程执行体,call () 方法比 run () 方法功能要强大。

    • call () 方法可以有返回值

    • call () 方法可以声明抛出异常

  • Java5 提供了 Future 接口来代表 Callable 接口里 call () 方法的返回值,并且为 Future 接口提供了一个实现类 FutureTask,这个实现类既实现了 Future 接口,还实现了 Runnable 接口,因此可以作为 Thread 类的 target。在 Future 接口里定义了几个公共方法来控制它关联的 Callable 任务。

boolean cancel (boolean mayInterruptIfRunning):视图取消该 Future 里面关联的 Callable 任务

V get ():返回 Callable 里 call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

V get (long timeout,TimeUnit unit):返回 Callable 里 call()方法的返回值,最多阻塞 timeout 时间,经过指定时间没有返回抛出 TimeoutException

boolean isDone ():若 Callable 任务完成,返回 True

boolean isCancelled ():如果在 Callable 任务正常完成前被取消,返回 True

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

  1. 创建 Callable 接口的实现类,并实现 call () 方法,然后创建该实现类的实例(从 java8 开始可以直接使用 Lambda 表达式创建 Callable 对象)。

  2. 使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了 Callable 对象的 call () 方法的返回值

  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动线程(因为 FutureTask 实现了 Runnable 接口)

  4. 调用 FutureTask 对象的 get () 方法来获得子线程执行结束后的返回值

public class MyThreadTest {
    public static void main(String[] args) throws Exception{
        FutureTask target = new FutureTask<>(new MyRunnable(), null);
        Thread thread = new Thread(target);
        thread.start();
        target.get();
    }
}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 做一些事情
    }
}
  1. 1.5 后引入的 Executor 框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把 Task 描述清楚,然后提交即可。这个 Task 是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个 Callable 对象给 ExecutorService(如最常用的线程池 ThreadPoolExecutor),将得到一个 Future 对象,调用 Future 对象的 get 方法等待执行结果就好了。Executor 框架的内部使用了线程池机制,它在 java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在 Java 5 之后,通过 Executor 来启动线程比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题 —— 如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用 Executor 在构造器中。
public static void main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                // 写点逻辑
            }
        };
        // 执行 Runnable 接口,有返回值
        Future<?> submit = executorService.submit(runnable);
        submit.get();
        // 执行 Runnable 接口,无返回值
        executorService.execute(runnable);
        // 执行 Callable 接口,有返回值
        Callable<String> stringCallable = new Callable(){
            @Override
            public Object call() throws Exception {
                // 写逻辑
                return null;
            }
        };
        Future<String> submit1 = executorService.submit(stringCallable);
        submit1.get();
    }