Java並發編程:Java實現多線程的幾種方式
- 2019 年 10 月 7 日
- 筆記
在Java中,多線程主要的實現方式有四種:繼承Thread類、實現Runnable接口、實現Callable接口通過FutureTask包裝器來創建Thread線程、使用ExecutorService、Callable、Future實現有返回結果的多線程。其中前兩種方式線程執行完後都沒有返回值,而後兩種是帶返回值的。除此之外,通過Timer啟動定時任務,或者通過像Spring Task和quartz這樣的第三方任務調度框架也可以開啟多線程任務。
1、繼承Thread類創建線程
Thread類本質上也是實現了Runnable接口的一個實例,代表一個線程的實例。啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程比較簡單,通過繼承Thread類並複寫run()方法,就可以啟動新線程並執行自己定義的run()方法。
CreateThreadDemo1.java
public class CreateThreadDemo1 extends Thread { public CreateThreadDemo1(String name) { // 設置當前線程的名字 this.setName(name); } @Override public void run() { System.out.println("當前運行的線程名為: " + Thread.currentThread().getName()); } public static void main(String[] args) throws Exception { // 注意這裡,要調用start方法才能啟動線程,不能調用run方法 new CreateThreadDemo1("MyThread1").start(); new CreateThreadDemo1("MyThread2").start(); } }
輸出結果:
當前運行的線程名為: MyThread1 當前運行的線程名為: MyThread2
2、實現Runnable接口創建線程 由於Java是單繼承機制,如果自己的類已經繼承自另一個類,則無法再直接繼承Thread類,此時,可以通過實現Runnable接口來實現多線程。
實現Runnable接口並實現其中的run方法,然後通過構造Thread實例,傳入Runnable實現類,然後調用Thread的start方法即可開啟一個新線程。
CreateThreadDemo2.java
public class CreateThreadDemo2 implements Runnable { @Override public void run() { System.out.println("當前運行的線程名為: " + Thread.currentThread().getName()); } public static void main(String[] args) throws Exception { CreateThreadDemo2 runnable = new CreateThreadDemo2(); new Thread(runnable, "MyThread1").start(); new Thread(runnable, "MyThread2").start(); } }
輸出結果:
當前運行的線程名為: MyThread1 當前運行的線程名為: MyThread2
3、實現Callable接口通過FutureTask包裝器來創建Thread線程
首先需要一個實現Callable接口的實例,然後實現該接口的唯一方法call邏輯,接着把Callable實例包裝成FutureTask傳遞給Thread實例啟動新線程。FutureTask本質上也實現了Runnable接口,所以同樣可以用來構造Thread實例。
CreateThreadDemo3.java
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CreateThreadDemo3 { public static void main(String[] args) throws Exception { // 創建線程任務,lambada方式實現接口並實現call方法 Callable<Integer> callable = () -> { System.out.println("線程任務開始執行了..."); Thread.sleep(2000); return 1; }; // 將任務封裝為FutureTask FutureTask<Integer> task = new FutureTask<>(callable); // 開啟線程,執行線程任務 new Thread(task).start(); // ==================== // 這裡是在線程啟動之後,線程結果返回之前 System.out.println("線程啟動之後,線程結果返回之前..."); // ==================== // 為所欲為完畢之後,拿到線程的執行結果 Integer result = task.get(); System.out.println("主線程中拿到異步任務執行的結果為:" + result); } }
輸出結果:
線程啟動之後,線程結果返回之前... 線程任務開始執行了... 主線程中拿到異步任務執行的結果為:1
4、使用ExecutorService、Callable、Future實現有返回結果的線程(線程池方式)
ExecutorService、Callable、Future三個接口都是屬於Executor框架。可返回值的任務必須實現Callable接口。通過ExecutorService執行Callable任務後,可以獲取到一個Future的對象,在該對象上調用get()就可以獲取到Callable任務返回的結果了。
注意:Future的get方法是阻塞的,即:線程無返回結果,get方法會一直等待。
CreateThreadDemo4.java
import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CreateThreadDemo4 { @SuppressWarnings({ "rawtypes", "unchecked" }) public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("---- 主程序開始運行 ----"); Date startTime = new Date(); int taskSize = 5; // 創建一個線程池,Executors提供了創建各種類型線程池的方法,具體詳情請自行查閱 ExecutorService executorService = Executors.newFixedThreadPool(taskSize); // 創建多個有返回值的任務 List<Future> futureList = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable callable = new MyCallable(i); // 執行任務並獲取Future對象 Future future = executorService.submit(callable); futureList.add(future); } // 關閉線程池 executorService.shutdown(); // 獲取所有並發任務的運行結果 for (Future future : futureList) { // 從Future對象上獲取任務的返回值,並輸出到控制台 System.out.println(">>> " + future.get().toString()); } Date endTime = new Date(); System.out.println("---- 主程序結束運行 ----,程序運行耗時【" + (endTime.getTime() - startTime.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private int taskNum; MyCallable(int taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>> " + taskNum + " 線程任務啟動"); Date startTime = new Date(); Thread.sleep(1000); Date endTime = new Date(); long time = endTime.getTime() - startTime.getTime(); System.out.println(">>> " + taskNum + " 線程任務終止"); return taskNum + "線程任務返回運行結果, 當前任務耗時【" + time + "毫秒】"; } }
輸出結果:
---- 主程序開始運行 ---- >>> 0 線程任務啟動 >>> 1 線程任務啟動 >>> 2 線程任務啟動 >>> 3 線程任務啟動 >>> 4 線程任務啟動 >>> 0 線程任務終止 >>> 1 線程任務終止 >>> 0線程任務返回運行結果, 當前任務耗時【1001毫秒】 >>> 1線程任務返回運行結果, 當前任務耗時【1001毫秒】 >>> 4 線程任務終止 >>> 3 線程任務終止 >>> 2 線程任務終止 >>> 2線程任務返回運行結果, 當前任務耗時【1001毫秒】 >>> 3線程任務返回運行結果, 當前任務耗時【1001毫秒】 >>> 4線程任務返回運行結果, 當前任務耗時【1001毫秒】 ---- 主程序結束運行 ----,程序運行耗時【1009毫秒】
5、其他創建線程的方式
當然,除了以上四種主要的線程創建方式之外,也還有很多其他的方式可以啟動多線程任務。比如通過Timer啟動定時任務,或者通過像Spring Task和quartz這樣的第三方任務調度框架也可以開啟多線程任務,關於第三方任務調度框架的例子還請查詢相關資料。
源碼下載
碼云:https://gitee.com/liuge1988/java-demo.git
作者:朝雨憶輕塵 出處:https://www.cnblogs.com/xifengxiaoma/ 版權所有,歡迎轉載,轉載請註明原文作者及出處。