Java 多线程的创建


Java 多线程的创建和实现

线程的 3 + 1 种创建方式

1.继承 Thread 类

Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。

启动线程的唯一方法就是通过 Thread 类的 start() 实例方法。start() 方法是一个 native 方法,它将启动一个新线程,并执行 run() 方法。

public class ThreadTest extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread,重写run方法");
    }

    // 测试调用
    public static void main(String[] args) throws InterruptedException {

        ThreadTest threadTest = new ThreadTest();
        threadTest.start();
        Thread.sleep(3000);
    }
}

2.实现 Runnable 接口

public class ThreadRunnable implements Runnable { // 新建实现 Runnable 接口的类
    @Override
    public void run() {
        System.out.println("实现Runnable方法");
    }

    // 测试调用
    public static void main(String[] args) throws InterruptedException {
        ThreadRunnable threadTest2 = new ThreadRunnable();
        // 由 Runnable 的实例创建一个Thread对象
        Thread thread = new Thread(threadTest2);
        // 启动线程
        thread.start(); // 启动 ThreadTest2 这个线程实例
        thread.run();// 只是执行run这个方法
    }
}

新建实现 Runnable 接口的类ThreadRunnable —> 创建 该实现类的对象实例 —> 由Runnable的实现类对象创建一个Thread对象 —> 启动线程

至此,一个线程就创建完成了。

线程的执行流程很简单,当执行代码oneThread.start();时,就会执行oneRunnable对象中的void run();方法,

该方法执行完成后,线程就消亡了。

3.实现 callable 接口

public class ThreadTestCallable implements Callable<String> { // 新建实现 Callable 接口的类
    @Override
    public String call() throws Exception {
        System.out.println("实现callable接口");
        return "重写call方法返回一个对象";
    }

    public static void main(String[] args) throws Exception {
        // 创建一个类对象
        ThreadTestCallable threadTestCallable = new ThreadTestCallable();
        // Callable 比 runnable多了一步:创建了 callable实例后需要创建一个对应的 FutureTask 对象,再由这个对象FutureTask创建Thread
        FutureTask<String> stringFutureTask = new FutureTask<>(threadTestCallable);
        Thread thread = new Thread(stringFutureTask);
        thread.start(); // 启动 ThreadTest2 这个线程实例
    }

}

创建实现Callable接口的类ThreadTestCallable —> 创建ThreadTestCallable该实现类的对象实例 —> 由 ThreadTestCallable 实例创建一个FutureTask对象 —> 由FutureTask的实例创建一个Thread对象 —> 启动线程

注意:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。

至此,一个线程就创建完成了。


三种方式相同点: 都采用Thread.start()启动线程

不同点:

  • 继承 Thead 类实现多线程,可以直接通过实例对象调 start() 启动线程来执行。但实现 Callable和Runnable无法直接调用 start()。

    • 实现Runnable接口后,在创建实例对象后还不能直接调 start(),Runnable 需要 new Thread(Runnable实例对象)包装一下再调用;
    • Callable 比 Runnable 还多一步,需要先通过 FutureTask 对象包装实例,再用 new Thread(FutureTask实例对象),然后才调用start() 方法。
  • Callable规定的方法是call(),Runnable规定的方法是run()。

    • 其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
  • Callable的任务执行后有返回值,而Runnable的任务是没有返回值的。

  • call() 方法可以抛出异常,run() 方法不可以。

  • 运行Callable任务可以拿到一个Future对象,c表示异步计算的结果。

注:Callalbe接口支持返回执行结果,需要调用 FutureTask.get() 得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

4.基于线程池的方式

参考文章:https://blog.csdn.net/weixin_44814196/article/details/140997805

步骤1:创建线程池:

  ExecutorService executor = Executors.newCachedThreadPool();

步骤2:通过将 Runnable对象或Callable对象将要执行的任务提交给线程池的ExecutorService对象

executor.submit(threadTestCallable实例对象);

步骤3:关闭线程池,等待所有任务完成

// 关闭线程池,等待所有任务完成
executor.shutdown();
public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        // ThreadTestCallable实例对象
        ThreadTestCallable threadTestCallable = new ThreadTestCallable();
        // 提交实例对象,执行实例中的任务
        executor.submit(threadTestCallable);
        executor.shutdown();
    }
}

注意:shutdown 方法并不会立即停止正在运行的任务,而是不允许提交新的任务。

JAVA-技能点
知识点