一、什么是線程?
線程是操作系統能夠進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運作單位。簡單理解就好比我們坐高鐵、飛機過安檢一樣,過安檢的時候一個入口只有一個安檢口,而多線程就是為安檢開啟了多個安檢口。Java在語言層面對多線程提供了卓越的支持。
二、線程和進程有什么區別?
線程是進程的子集,一個進程可以有很多線程,每條線程並行執行不同的任務。不同的進程使用不同的內存空間,而所有的線程共享一片相同的內存空間。每個線程都擁有單獨的棧內存用來存儲本地數據。
三、 如何在Java中實現線程?
線程實現的三種方式:
- 繼承Thread類創建線程類
步驟:
===> 創建一個繼承於Thread類的子類
===> 重寫Thread類的run()方法
===> 創建Thread類的子類的對象
===> 通過此對象調用start()方法啟動線程
public class FirstThreadTest extends Thread{ //重寫run方法,run方法內就是線程要執行的業務邏輯代碼 public void run(){ for(int i = 0;i<100;i++){ System.out.println(getName()+" "+i); } } public static void main(String[] args){ for(int i = 0;i< 100;i++){ // Thread.currentThread()方法返回當前正在執行的線程對象,getName()方法返回線程名稱,此處線程暫未開啟,所以打印的是Main線程名 System.out.println(Thread.currentThread().getName()+" :"+i); if(i==20){ // 啟動連個線程 new FirstThreadTest().start(); new FirstThreadTest().start(); } } } }
- 通過Runnable接口創建線程類 (常用)
步驟:
===> 定義Runnable接口實現類,並重寫run()方法【和Thread類中的run()方法是一樣的,都是來自Runnable接口的】
===> 創建實現類的對象,將實現類的對象作為參數傳遞到Thread類的構造器中
===> 通過Thread類對象調用start()方法啟動線程
public class RunnableThreadTest implements Runnable { public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if (i == 20) { // 創建實現類對象 RunnableThreadTest rtt = new RunnableThreadTest(); // 以參數的形式傳入Thread類的構造器中,並給當前線程重命名 new Thread(rtt, "新線程1").start(); new Thread(rtt, "新線程2").start(); } } } }
- 通過線程池實現多線程 (最常用且最實用)
在Java中juc包中有一個Executors工具類,可以為我們創建一個線程池,其本質就是new了一個ThreadPoolExecutor對象。
- Executors.newCachedThreadPool()(無界線程池,可以進行自動線程回收)
- Executors.newFixedThreadPool(int)(固定大小線程池)
- Executors.newSingleThreadExecutor()(單個后台線程)
- Executors.newScheduledThreadPool() (創建固定大小的線程,可以延遲或定時的執行任務)
線程池體系結構簡化圖:
Tips:藍色實線箭頭是類的繼承;綠色虛線箭頭是接口的實現,綠色實線箭頭是接口的繼承
Executors 工具類通過提供一系列的工廠方法來創建線程池,返回的線程池(就是ThreadPoolExecutor對象)都實現了 ExecutorService 接口,ExecutorService接口繼承了Executor接口,提供了更豐富的方法來管理線程池,ExecutorService對象可以通過execute(Runnable)或 submit(Callable)方法來開始執行新的方法。
ExecutorService(其實就是線程池)的生命周期包括三種狀態,運行,關閉,終止。創建后便進入運行狀態。調用shutdown() 方法就進入關閉狀態。此時ExecutorService不再接受新的任務,但是它還在執行已經提交的任務,等到所有的任務都執行完畢后,就到了終止狀態。
示例代碼:
public class ScheduledThreadTest { public static void main(String[] args) { System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》start"); // 創建支持計划任務的線程池 ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2); // delay:延遲 延遲兩秒執行任務 threadPool.schedule(() -> System.out.println("定時任務"),2, TimeUnit.SECONDS); System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》end"); threadPool.shutdown(); } }
輸出結果:(通過實際輸出,可以看到 ,兩秒鍾之后,才開始執行任務當中的打印 語句)
=============================
周期性的定時任務示例代碼:
public class ScheduledThreadTest2 { public static void main(String[] args) { System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》start"); // 創建支持計划任務的線程池 ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(2); /** * scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit) * 第一個command參數是任務實例, * 第二個initialDelay參數是初始化延遲時間, * 第三個delay參數是延遲間隔時間, * 第四個unit參數是時間單元。 */ threadPool.scheduleWithFixedDelay(new Runnable() { int count = 0; @Override public void run() { System.out.println("count = " + count++); } }, 3, 2, TimeUnit.SECONDS); System.out.println("當前線程: " + Thread.currentThread().getName() + "===========》end"); // threadPool.shutdown(); // 不能調用此方法,否則定時任務無法啟動(延期3秒啟動,但是線程池已經關閉了) } }
輸出結果:
…..后面每間隔2s,輸出一次。
Callable接口
現在我們知道了怎么創建線程:一種是通過創建Thread類,另一種是通過使用Runnable創建線程。但是,Runnable缺少的一項功能是,當線程終止時(即run()完成時),我們無法使線程返回結果。為了支持此功能,Java中提供了Callable,Callable接口使用泛型去定義它的返回類型。
- 為了實現Runnable,需要實現不返回任何內容的run()方法,而對於Callable,需要實現在完成時返回結果的call()方法。請注意,不能使用Callable創建線程,只能使用Runnable創建線程。
- 另一個區別是call()方法允許拋出異常,而run()則不能。
- 為實現Callable而必須重寫call方法。
Future接口
當call()方法完成時,結果必須存儲在主線程已知的對象中,以便主線程可以知道該線程返回的結果。為此,可以使用Future對象。將Future視為保存結果的對象–它可能暫時不保存結果,但將來會保存(一旦Callable返回)。因此,Future基本上是主線程可以跟蹤進度以及其他線程的結果的一種方式。
Executors工具類提供了一些有用的方法在線程池中執行Callable內的任務。由於Callable任務是並行的(並行就是整體看上去是並行的,其實在某個時間點只有一個線程在執行),我們必須等待它返回的結果。 java.util.concurrent.Future對象為我們解決了這個問題。在線程池提交Callable任務后返回了一個Future對象,使用它可以知道Callable任務的狀態和得到Callable返回的執行結果。Future提供了get()方法讓我們可以等待Callable結束並獲取它的執行結果。
要實現此接口,必須重寫5種方法:
要創建線程,需要Runnable。為了獲得結果,需要future。Java庫具有具體的FutureTask類型,該類型實現Runnable和Future,並將這兩種功能組合在一起。可以通過為其構造函數提供Callable來創建FutureTask。然后,將FutureTask對象提供給Thread的構造函數以創建Thread對象。因此,間接地使用Callable創建線程。
使用Callable和Future的完整示例:
import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.*; public class TestCallable implements Callable<Object> { private int taskNum; public TestCallable(int taskNum) { this.taskNum = taskNum; } //1,2主要區別是創建線程的方式 public static void main(String[] args) throws ExecutionException, InterruptedException { test1(); test2(); } /** * 使用Executors.newFixedThreadPool創建線程池 * @throws InterruptedException * @throws ExecutionException */ private static void test1() throws InterruptedException, ExecutionException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize = 5; ExecutorService pool = Executors.newFixedThreadPool(taskSize); List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new TestCallable(i); // 執行任務並獲取Future對象 Future f = pool.submit(c); list.add(f); } // 關閉線程池 pool.shutdown(); // 獲取所有並發任務的運行結果 for (Future f : list) { // 從Future對象上獲取任務的返回值,並輸出到控制台 System.out.println(">>>" + f.get().toString()); //OPTION + return 拋異常 } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } /** * 線程直接使用new Thread來創建 * @throws ExecutionException * @throws InterruptedException */ private static void test2() throws ExecutionException, InterruptedException { System.out.println("----程序開始運行----"); Date date1 = new Date(); int taskSize = 5; FutureTask[] randomNumberTasks = new FutureTask[5]; List<Future> list = new ArrayList<Future>(); for (int i = 0; i < randomNumberTasks.length; i++) { Callable c = new TestCallable(i); // 執行任務並獲取Future對象 randomNumberTasks[i] = new FutureTask(c); Thread t = new Thread(randomNumberTasks[i]); t.start(); } // 獲取所有並發任務的運行結果 for (Future f : randomNumberTasks) { // 從Future對象上獲取任務的返回值,並輸 System.out.println(">>>" + f.get().toString()); //OPTION + return 拋異常 } Date date2 = new Date(); System.out.println("----程序結束運行----,程序運行時間【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } /** * call方法的實現,主要用於執行線程的具體實現,並返回結果 * @return * @throws Exception */ @Override public Object call() throws Exception { System.out.println(">>>" + taskNum + "任務啟動"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任務終止"); return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】"; } }
運行結果: