java併發程式設計 | 執行緒詳解
個人網站: https://chenmingyu.top/concurrent-thread/
程序與執行緒
程序:作業系統在執行一個程式的時候就會為其建立一個程序(比如一個java程式),程序是資源分配的最小單位,一個程序包含多個執行緒
執行緒:執行緒是cpu排程的最小單位,每個執行緒擁有各自的計數器,對戰和區域性變數等屬性,並且能過訪問共享的記憶體變數
執行緒的狀態
java執行緒的生命週期總共包括6個階段:
- 初始狀態:執行緒被建立,但是還沒有呼叫
start()
方法 - 執行狀態:java中將就緒狀態和執行狀態統稱為執行狀態
- 阻塞狀態:執行緒阻塞,執行緒等待進入
synchronized
修飾的程式碼塊或方法 - 等待狀態:執行緒進入等待狀態,需要呼叫
notify()
或notifyAll()
進行喚醒 - 超時等待狀態:執行緒進入等待狀態,在指定時間後自行返回
- 終止狀態:執行緒執行完畢
在某一時刻,執行緒只能處於其中的一個狀態
執行緒初始化後,呼叫 start()
方法變為執行狀態,呼叫 wait()
, join()
等方法,執行緒由執行狀態變為等待狀態,呼叫 notify()
或 notifyAll()
等方法,執行緒由等待狀態變成執行狀態,超時等待狀態就是在等待狀態基礎上加了時間限制,超過規定時間,自動更改為執行狀態,當需要執行同步方法時,如果沒有獲得鎖,這時執行緒狀態就變為阻塞狀態,直到獲取到鎖,變為執行狀態,當執行完執行緒的 run()
方法後,執行緒變為終止狀態
建立執行緒
建立執行緒有三種方式
Thread Runnable Callable
繼承 Thread
類
/** * @author: chenmingyu * @date: 2019/4/8 15:13 * @description: 繼承Thread類 */ public class ThreadTest extends Thread{ @Override public void run() { IntStream.range(0,10).forEach(i->{ System.out.println(this.getName()+":"+i); }); } public static void main(String[] args) { Thread thread = new ThreadTest(); thread.start(); } }
實現 Runnable
介面
/** * @author: chenmingyu * @date: 2019/4/8 15:18 * @description: 實現Runnable介面 */ public class RunnableTest implements Runnable { @Override public void run() { IntStream.range(0,10).forEach(i->{ System.out.println(Thread.currentThread().getName()+":"+i); }); } public static void main(String[] args) { Runnable runnable = new RunnableTest(); new Thread(runnable,"RunnableTest").start(); } }
實現 Callable
介面
/** * @author: chenmingyu * @date: 2019/4/8 15:23 * @description: 實現Callable介面 */ public class CallableTest implements Callable<Integer> { @Override public Integer call() throws Exception { IntStream.range(0,10).forEach(i->{ System.out.println(Thread.currentThread().getName()+":"+i); }); return -1; } public static void main(String[] args) throws Exception { Callable callable = new CallableTest(); FutureTask futureTask = new FutureTask(callable); new Thread(futureTask,"future").start(); System.out.println("result:"+futureTask.get()); } }
執行緒的暫停,恢復,停止
不安全的執行緒暫停,恢復,停止操作
Thread
提供的過期方法可以實現對執行緒進行暫停 suspend()
,恢復 resume()
,停止 stop()
的操作
例:建立一個執行緒, run()
中迴圈輸出當前時間,在 main()
方法中對新建執行緒進行暫停,恢復,停止的操作
/** * @author: chenmingyu * @date: 2019/4/8 15:51 * @description: 執行緒的暫停,恢復,停止 */ public class OperationThread implements Runnable{ @Override public void run() { while (true){ try { TimeUnit.SECONDS.sleep(1L); System.out.println(Thread.currentThread().getName()+"執行中:"+LocalTime.now()); }catch (InterruptedException e){ System.err.println(e.getMessage()); } } } public static void main(String[] args) throws Exception{ Runnable runnable = new OperationThread(); Thread thread = new Thread(runnable,"operationThread"); /** * 啟動,輸出當前時間 */ thread.start(); TimeUnit.SECONDS.sleep(3L); /** * 執行緒暫停,不在輸出當前時間 */ System.out.println("此處暫停:"+LocalTime.now()); thread.suspend(); TimeUnit.SECONDS.sleep(3L); /** * 執行緒恢復,繼續輸出當前時間 */ System.out.println("此處恢復:"+LocalTime.now()); thread.resume(); TimeUnit.SECONDS.sleep(3L); /** * 執行緒停止,不在輸出當前時間 */ thread.stop(); System.out.println("此處停止:"+LocalTime.now()); TimeUnit.SECONDS.sleep(3L); } }
輸出
因為是過期方法,所以不推薦使用,使用
suspend()
方法後,執行緒不會釋放已經佔有的資源,就進入睡眠狀態,容易引發死鎖問題,而使用 stop()
方法終結一個執行緒是不會保證執行緒的資源正常釋放的,可能會導致程式異常
安全的執行緒暫停,恢復,停止操作
執行緒安全的暫停,恢復操作可以使用等待/通知機制代替,安全的停止操作可以用執行緒是否被中斷進行判斷
安全的執行緒暫停,恢復(等待/通知機制)
相關方法:
方法名 | 描述 |
---|---|
notify() | 通知一個在物件上等待的執行緒,使其重wait()方法中返回,前提是該執行緒獲得了物件的鎖 |
notifyAll() | 通知所有等待在該物件上的執行緒 |
wait() | 呼叫該方法執行緒進入等待狀態,只有等待另外執行緒的通知或被中斷才會返回,呼叫該方法會釋放物件的鎖 |
wait(long) | 超時等待一段時間(毫秒),如果超過時間就返回 |
wait(long,int) | 對於超時時間耕細粒度的控制,可以達到納秒 |
例:建立一個名為 waitThread
的執行緒,在 run()
方法,使用中使用 synchronized
進行加鎖,以變數 flag
為條件進行 while
迴圈,在迴圈中呼叫 LOCK.wait()
方法,此時會釋放物件鎖,由 main()
方法獲得鎖,呼叫 LOCK.notify()
方法通知 LOCK
物件上等待的 waitThread
執行緒,將其置為阻塞狀態,並將變數 flag
置為 true
,當 waitThread
執行緒再次獲取物件鎖之後繼續執行餘下程式碼
/** * @author: chenmingyu * @date: 2019/4/8 20:00 * @description: wait/notify */ public class WaitNotifyTest { private static Object LOCK = new Object(); private static Boolean FLAG = Boolean.TRUE; public static void main(String[] args) throws InterruptedException{ Runnable r = new WaitThread(); new Thread(r,"waitThread").start(); TimeUnit.SECONDS.sleep(1L); synchronized (LOCK){ System.out.println(Thread.currentThread().getName()+"喚醒waitThread執行緒:"+LocalTime.now()); /** * 執行緒狀態由等待狀態變為阻塞狀態 */ LOCK.notify(); /** * 只有當前執行緒釋放物件鎖,waitThread獲取到LOCK物件的鎖之後才會從wait()方法中返回 */ TimeUnit.SECONDS.sleep(2L); FLAG = Boolean.FALSE; } } public static class WaitThread implements Runnable { @Override public void run() { /** * 加鎖 */ synchronized (LOCK){ while (FLAG){ try { System.out.println(Thread.currentThread().getName()+"執行中:"+LocalTime.now()); /** * 執行緒狀態變為等待狀態 */ LOCK.wait(); /** * 再次獲得物件鎖之後,才會執行 */ System.out.println(Thread.currentThread().getName()+"被喚醒:"+LocalTime.now()); }catch (InterruptedException e){ System.err.println(e.getMessage()); } } } System.out.println(Thread.currentThread().getName()+"即將停止:"+LocalTime.now()); } } }
輸出
可以看到在
mian
執行緒呼叫 LOCK.notify()
方法後,沉睡了2s才釋放物件鎖, waitThread
執行緒在獲得物件鎖之後執行餘下程式碼
安全的執行緒停止操作(中斷標識)
執行緒的安全停止操作是利用執行緒的中斷標識來實現,執行緒的中斷屬性表示一個執行彙總的執行緒是否被其他執行緒進行了中斷操作,其他執行緒通過呼叫該執行緒的 interrupt()
方法對其進行中斷操作,而該執行緒通過檢查自身是否被中斷來進行響應,當一個執行緒被中斷可以使用 Thread.interrupted()
方法對當前執行緒的中斷標識位進行復位
例:新建一個執行緒, run
方法中使用 Thread.currentThread().isInterrupted()
是否中斷作為判斷條件,在主執行緒中使用 thread.interrupt()
方法對子執行緒進行中斷操作,用來達到終止執行緒的操作,這種方式會讓子執行緒可以去清理資源或一些別的操作,而使用 stop()
方法則會會直接終止執行緒
/** * @author: chenmingyu * @date: 2019/4/8 20:47 * @description: 中斷 */ public class InterruptTest { public static void main(String[] args) throws InterruptedException { Runnable r = new StopThread(); Thread thread = new Thread(r,"stopThread"); thread.start(); TimeUnit.SECONDS.sleep(1L); System.out.println(Thread.currentThread().getName()+"對stopThread執行緒進行中斷:"+LocalTime.now()); thread.interrupt(); } public static class StopThread implements Runnable { @Override public void run() { while (!Thread.currentThread().isInterrupted()){ System.out.println(Thread.currentThread().getName()+"執行中:"+LocalTime.now()); } System.out.println(Thread.currentThread().getName()+"停止:"+LocalTime.now()); } } }
未完待續...