目錄
一、線程的狀態
二、線程安全
一、線程的狀態
1.NEW Thread:對象創建好了,但是還沒有調用 start 方法在系統中創建線程。
2.TERMINATED:?Thread 對象仍然存在,但是系統內部的線程已經執行完畢了。
3.RUNNABLE: 就緒狀態,表示這個線程正在 cpu 上執行,或者準備就緒隨時可以去 pu 上執行。
4.TIMED WAITING: 指定時間的阻塞.就在到達一定時間之后自動解除阻塞使用 sleep 會進入這個狀態 使用帶有超時時間的join也會。
5.WAITING: 不帶時間的阻塞(死等),必須要滿足一定的條件,才會解除阻塞join 或者 wait 都會進入 WAITING。
6.BLOCKED: 由于鎖競爭,引起的阻塞.(后面線程安全的時候具體介紹)
這六種狀態在生命周期的大概位置,如圖:
用代碼形式測試 NEW 、RUNNABLE、TERMINATED 狀態
代碼:
public class ThreadDemo3 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 5000; i++) {count++;}System.out.println("count: " + count);});//t線程開始前System.out.println("t線程開始前: " + t.getState());t.start();//t線程執行中System.out.println("t線程執行中: " + t.getState());//t線程結束后t.join();System.out.println("t線程結束后: " + t.getState());} }
執行效果:
學習這些狀態,最大的作用就是調試多線程代碼bug的時候,給我們提供重要的參考依據,當某個程序卡住了,也就是意味著一些關鍵的線程阻塞了,我們就可以觀察線程的狀態,分享出一些原因。
注意:一個線程只能start一次,也就是說只有是NEW狀態的線程才能start。
二、線程安全
線程安全:某個代碼,不管它是單個線程執行,還是多個線程執行,都不會產生bug,這個情況就成為“線程安全”。
線程不安全:某個代碼,它單個線程執行,不會產生bug,但是多個線程執行,就會產生bug,這個情況就成為 “線程不安全”,或者 “存在線程安全問題”。
舉個線程不安全例子,現在,我們想計算一個變量的自增次數,它循環了100000次,用兩個線程去計算,各自計算循環50000次的次數。
代碼:
public class ThreadDemo4 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 1; i < 50000; i++) {count++;}});Thread t2 = new Thread(() -> {for (int i = 50000; i <= 100000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("count: " + count);} }
我們知道,從1到10_0000,肯定是自增了10_0000次,但是我們看看輸出結果如何?
答案卻不是10_0000,是50000多次,這是為何呢?原因就是多線程代碼,它們是并發執行的。
這個count++是由cpu的三個指令構成的:
(1)load 從內存中讀取數據到cpu的寄存器中。
(2)add 把寄存器中的值 + 1。
(3)save 把寄存器中的值寫回到內存中。
因為上面兩個線程是并發執行的,那么 t1 和 t2 線程的執行順序就是無序的,他們可能同時讀取數據,自增完再+1,下面用圖示他們的一些可能性。
暫時就畫這么多,因為線程并發的執行結果個數是無數的,并不是簡單的排列組合就能窮舉出來,因為并發的原因,可能 t1 線程它執行了兩次,才執行一次 t2 線程,也可能更多,或者 t2 執行的次數更多,t1 線程只執行一次。就又要排列組合了,這些情況都是有可能的。
這時,t1 和 t2自增的時候,就可能拿的是同一個值,這兩線程的其中一個自增后,沒放回內存中,另一個線程就又拿了,這肯定是不符合我們預期的。
所以我們從上面的可能情況找,符合我們預期的效果就只有這兩個了,如圖:
那么這種情況也就是串行化執行,執行完 t1,再執行t2,或者兩個順序相反。
代碼:
public class ThreadDemo4 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 1; i < 50000; i++) {count++;}});Thread t2 = new Thread(() -> {try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}for (int i = 50000; i <= 100000; i++) {count++;}});t1.start();t2.start();t2.join();System.out.println("count: " + count);} }
執行效果:
但這樣就沒必要使用多線程了,因為串行化,一個線程就能搞定了,使用多線程也沒太大意義。