1、Java線程的創建方式
常見的Java線程的4種創建方式:
- 繼承Thread類
- 實現Runnable
- 通過ExecutorService和Callable<Class>實現由返回值的線程
- 基於線程池
1.1 繼承Thread類
Thread類實現Runnable接口並定義了操作線程的一些方法,可以通過繼承thread類的方式創建一個線程。
具體代碼如下:
(1)通過繼承thread類創建NewThread線程:
public class NewThread extends Thread { public void run(){ //具體業務邏輯實現 System.out.println("創建一個新線程"); } }
(2)實例化Newthread並啟動:
public class Main { public static void main(String[] args) { NewThread newThread = new NewThread(); newThread.start(); } }
1.2 實現Runnable接口
創建一個線程,最簡單的方法是創建一個實現 Runnable 接口的類。為了實現 Runnable,一個類只需要執行一個方法調用 run()。
具體代碼如下:
(1)通過實現Runnable接口的方式創建MyRunnable線程:
public class MyRunnable implements Runnable{ public void run() { System.out.println("通過Runnable創建一個線程"); } }
(2)實例化MyRunnable對象,並創建一個線程對象傳入已經實例化MyRunnable實例:
public class Main { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); } }
1.3 通過ExecutorService和Callable<Class>實現有返回值的線程
在主線程中開啟多個線程並發執行一個任務,收集各個線程執行返回的結果並將最終結果匯總起來,就應該使用Callable接口。
具體代碼如下:
(1)通過Callable接口創建MyCallable線程:
import java.util.concurrent.Callable; public class MyCallable implements Callable<String> { private String name; public MyCallable(String name){ this.name = name; }; public String call() throws Exception { //call方法內實現邏輯 return name; } }
(2)創建一個線程池接收MyCallable實例:
public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { //創建一個固定大小為5的線程池 ExecutorService pool = Executors.newFixedThreadPool(5); //創建多個有返回值的任務列表 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < 5; i++) { //創建一個有返回值的線城實例 Callable callable = new MyCallable(i + ""); //提交線程,獲取Future對象 Future future = pool.submit(callable); System.out.println("提交Callable線程" + i); list.add(future); } pool.shutdown(); for (Future future:list) { System.out.println("獲得Callable線程結果"+future.get().toString()); } } }
結果:
提交Callable線程0
提交Callable線程1
提交Callable線程2
提交Callable線程3
提交Callable線程4
獲得Callable線程結果0
獲得Callable線程結果1
獲得Callable線程結果2
獲得Callable線程結果3
獲得Callable線程結果4
1.4 基於線程池創建線程
為了避免浪費線程資源,可以使用線程池來創建線程。
具體代碼如下:
package com.jzq.concurrent.Thread.ThreadPool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(7); //提交多個線程任務並執行 for (int i = 0; i < 7; i++) { threadPool.execute(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName()+"在運行"); } } ); } } }
結果:
pool-1-thread-1在運行 pool-1-thread-4在運行 pool-1-thread-3在運行 pool-1-thread-2在運行 pool-1-thread-5在運行 pool-1-thread-6在運行 pool-1-thread-7在運行
2、線程的生命周期
線程的生命周期分為新建、就緒、運行、阻塞和死亡這5種狀態。
具體狀態轉換如圖:
其流程如下:
- 調用new方法新建一個線程,這時線程處於新建狀態。
- 調用一個start方法啟動一個線程,這時線程處於就緒狀態。
- 處於就緒狀態的線程等待線程獲取CPU資源,在等待其獲取CPU資源后線程會執行run方法進入運行狀態。
- 正在運行的線程在調用了yield方法或失去處理器資源時,會再次進入就緒狀態。
- 正在執行的線程在執行了sleep方法、I/O阻塞、等待同步鎖、等待通知、調用suspend方法等操作后,會掛起並進入阻塞狀態,進入Blocked池。
- 阻塞狀態的線程由於出現sleep時間已到、I/O方法返回、獲得同步鎖、收到通知、調用resume方法等情況,會再次進入就緒狀態,等待CPU時間片的輪詢。該線程在獲取CPU資源后,會再次進入運行狀態。
- 處於運行狀態的線程,在調用run方法或call方法正常執行完成、調用stop方法停止線程或者程序執行錯誤導致異常退出時,會進入死亡狀態。
2.1 新建狀態:New
在Java中使用new關鍵字創建一個線程,新創建的線程將處於新建狀態。在創建線程時主要是為線程分配內存並初始化其成員變量的值。
2.2 就緒狀態:Runnable
新建的線程對象在調用start方法之后將轉為就緒狀態。此時JVM完成了方法調用棧和程序計數器的創建,等待該線程的調度和運行。
2.3 運行狀態:Running
就緒狀態的線程在競爭到CPU的使用權並開始執行run方法的線程執行體時,會轉為運行狀態,處於運行狀態的線程的主要任務就是執行run方法中的邏輯代碼。
2.4 阻塞狀態:Blocked
運行中的線程會主動或被動地放棄CPU的使用權並暫停運行,此時該線程將轉為阻塞狀態,直到再次進入可運行狀態,才有機會再次競爭到CPU使用權並轉為運行狀態。
阻塞狀態分為三種:
- 等待阻塞:在運行狀態的線程調用object.wait方法時,JVM會把該線程放入等待隊列中,線程轉為阻塞狀態。
- 同步阻塞:在運行狀態的線程嘗試獲取正在被其他線程占用的對象同步鎖時,JVM會把該線程放入鎖池中,此時線程轉為阻塞狀態。
- 其他阻塞:運行狀態的線程在執行Thread.sleep(long ms)、Thread.join()或者發出I/O請求時,JVM會把該線程轉為阻塞狀態。直到sleep()狀態超時、Thread.join()等待線程終止或超時,或者I/O處理完畢,線程才重新轉為可運行狀態。
2.5 死亡狀態:Dead
線程在以下三種方式結束后轉為死亡狀態:
- 線程正常結束:run方法或call方法執行完成。
- 線程異常結束:運行中的線程拋出一個Error或未捕獲的Exception,線程異常退出。
- 手動結束:調用線程對象stop方法手動結束運行中的線程(會導致鎖混亂和死鎖,不推薦)。
3、線程的基本方法
線程的基本方法有wait、notify、notifyAll、sleep、join、yield等,這些方法控制線程的運行,影響線程變化。
3.1 線程等待:wait方法
調用wait方法的線程會進入WAITING狀態,只有等到其他線程的通知或被中斷后才會返回。在調用wait方法后會釋放對象鎖,會導致當前線程進入Waiting狀態。
3.2 線程睡眠:sleep方法
調用sleep方法會導致當前線程休眠。sleep方法不會釋放當前占用的鎖,會導致線程進入Time-Waiting狀態。
3.3 線程讓步:yield方法
調用yield方法會使當前線程讓出(釋放)CPU執行時間片,與其他線程一起競爭CPU時間片。
3.4 線程中斷:interrupt方法
interrupt方法用於向線程發送一個終止通知信號,會影響該線程內部的一個中斷標識位,線程並不會因為調用了interrupt方法而改變狀態。狀態的具體變化需要等待接收到中斷標識的程序的最終處理結果來判定。
3.5 線程加入:join方法
join方法用於等待其他線程終止,如果在當前線程中調用一個線程join方法,則當前線程轉為阻塞狀態,等待另一個線程結束,當前線程再由阻塞狀態轉為就緒狀態,等待獲取CPU的使用權。
3.6 線程喚醒:notify方法
notify方法用於喚醒在此對象監視器上等待的一個線程,如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程,選擇是任意的。
3.7 后台守護線程:setDaemon方法
setDaemon方法用於定義一個守護線程,該線程是后台線程,其特征為用戶線程提供公共服務,在沒有用戶線程可服務時會自動離開。
3.8 終止線程的4種方式:
1、正常運行結束
指線程體執行完成,線程自動結束。
2、使用退出標志退出線程
一般情況下,在run方法執行完畢時,線程會正常結束。但有些線程需要長時間運行,只有在系統滿足默寫特殊條件后,才能觸發關閉這些線程。
比如設置一個boolean類型的標志,並通過設置這個標志為true或false來控制while循環是否退出,代碼如下:
public class ThreadSafe extends Thread { public volatile boolean exit = false; public void run(){ while (!exit){ //TODO 執行業務邏輯代碼 } } }
在定義exit時使用了關鍵字volatile,volatile用於時exit線程同步安全。
3、使用interrupt方法終止線程
使用interrupt方法終止線程有以下兩種情況:
(1)線程處於阻塞狀態。在線程處於阻塞狀態時,在調用線程的interrupt方法時,會拋出InterruptException異常,我們通過代碼捕獲異常,然后通過break跳出狀態循環結束run方法。
(2)線程處於非阻塞狀態。使用isInterrupted方法判斷線程的中斷標志來退出循環。調用interrupt方法時,中斷標志會被設置為true,不能立刻退出線程,而是執行線程終止前的資源釋放操作,等待資源釋放完畢后退出該線程。
具體代碼如下:
public class ThreadSafeDead extends Thread { public void run(){ while (!isInterrupted()){//在非阻塞過程中通過判斷中斷標志來退出 try { Thread.sleep(5*1000);// }catch(InterruptedException e){ e.printStackTrace(); break;//在捕獲到異常后執行break跳出循環 } } } }
4、使用stop方法終止線程:不安全
在程序使用Thread.stop方法終止線程時,該線程的子線程會拋出ThreadDeatherror錯誤,並釋放線程持有的所有鎖。