一.多線程的中斷
1.通過自定義的變量來作為標志位
import java.util.Scanner;public class Demo1 {public static boolean flg = false;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while (!flg){System.out.println("t1線程正在執行");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("t1線程被中斷");}}System.out.println("t1線程停止");});t1.start();Scanner scanner = new Scanner(System.in);scanner.next();flg = true;}
}
在代碼中,我們將 flg 作為標志位,當代碼運行的時候我們需要輸入任意鍵為就可以改變 flg,以此來達到停止代碼運行的作用;
注意:flg 必須作為全局變量,因為 lambda 捕獲的局部變量是不能被修改的,這是為了確保代碼的安全性和一致性,由于我們要對flg 進行修改,所以我們要將 flg 作為全局變量。
2.使用 Thread.interrupted() 或者
Thread.currentThread.isInterrupted()代替標志位
import java.util.Scanner;public class Demo2 {public static void main(String[] args) {Thread t1 = new Thread(()->{Thread cur = Thread.currentThread();while (!cur.isInterrupted()){System.out.println("t1線程正在執行");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("t1線程被中斷");}}System.out.println("t1線程停止");});t1.start();Scanner scanner = new Scanner(System.in);scanner.next();t1.interrupt();}
}
以上代碼我們將 cur.isInterrupted 作為標志位,當我們輸入任意字符之后就會調用 t1.interrupt() 方法,將標志位改為 false,但是運行后并且輸入后我們發現代碼仍在執行,
Java的線程中斷機制有這樣的設計:
當一個線程在阻塞狀態(Thread.join,Thread.sleep,Thread.wait)下被中斷時,中斷標志位會被自動清除,也就是說當調用 Thread.interrupt() 之后,雖然? !cur.isInterrupted() 改為了 false,但是由于被清除了標志位,又變為了 true,這才導致程序仍然在運行。
也就是說 interrupt 方法做了兩件事:首先將標志位設置為了 true,但是由于喚醒了 sleep ,以異常的方式返回了,清除了線程的標志位,將標志位重新設置為了 false。針對這種情況,我們需要在 catch 中做更詳細的處理。
二.線程的等待與安全問題
public class Demo3 {static int result = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});t1.start();t2.start();Thread.sleep(1000);System.out.println( result);}
}
以上代碼我們創建了兩個線程 : 分別是 t1,t2,這兩個線程同時對 result ++,正常情況下,result 的結果肯定是100000的,但在我們運行代碼后:
我們發現并沒有到達100000,這就是多線程引發的線程安全問題,線程對 result++ 要進行三個操作:首先將 result 從主內存中讀取到工作內存中,其次將 result 在工作內存中++,最后將result的值放回到主內存中,問題就出在這幾步上,由于操作不是原子的,導致在 t1 從主內存中讀取到 result的值并且++之后,t2也進行了從主內存中讀取到了 result 的值,此時t1 和t2都需要將工作內存中的值寫入到主內存當中,這樣就會導致其中一個線程的值會覆蓋掉另一個線程的值,這才導致了 result 的值并沒有到達100000;
針對這個問題:
我們可以讓某一個線程通過 Thread.sleep() 來等待另一個線程運行,但這種方法我們無法確定另一個線程運行多久,所以不推薦使用。
第二個方法是運行 join() 方法,
public class Demo3 {static int result = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});t1.start();t1.join();t2.start();Thread.sleep(1000);System.out.println( result);}
}
在 t2 運行之前調用 t1.join() 方法,此時main線程會等待 t1 線程運行完成之后再往下走調用 t2.start,此時就解決了上述問題。
三.線程的狀態
線程的狀態分為六種:
new,runnable,terminated 狀態演示:
public class Demo4 {public static void main(String[] args) {Thread t1 = new Thread(()->{for (int i = 0; i < 100; i++) {}});System.out.println(t1.getState());t1.start();System.out.println(t1.getState());try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t1.getState());}}
timed_waiting , blocked 狀態演示:
public class Demo5{static Object object = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized( object){try {Thread.sleep(10000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(()->{synchronized ( object){while ( true){}}});t1.start();t2.start();try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t1.getState());System.out.println(t2.getState());}
}
將上述部分代碼進行更改,就可以得到 waiting 狀態:
Thread t1 = new Thread(()->{synchronized( object){try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}});
四.線程的鎖
還是對上述的線程安全問題進行解決,線程安全問題其中的導火索之一便是代碼的非原子性,由于一件事情要分幾步來操作,這種情況容易出問題,所以就出現了鎖,鎖可以將幾個步驟的操作綁定在一起,這樣就可以變相的實現代碼的原子化。
public class Demo6 {static int result = 0;static Object locker = new Object();public static void add(int a){synchronized (locker){result++;}}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {add(result);}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {add(result);}});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}
sychronized(Object object){ }
其中 sychronized 就是鎖的關鍵字,他需要的參數是 Object 類,就像是對 object 上了鎖,其他人想要進是沒有辦法的,除非你釋放了鎖,當你走完{ }的時候,此時就完成了對鎖的釋放。
上述代碼我們對 add類里面的內容上了鎖,我們還可以對線程的內容直接加鎖:
public class Demo6 {static int result = 0;static Object locker = new Object();public static void add(int a){result++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}}});Thread t2 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}};});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}
運行結果不變;
要注意:我們不能只對一個線程加鎖,那樣的話是沒有意義的:
public class Demo6 {static int result = 0;static Object locker = new Object();public static void add(int a){result++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {add(result);};});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}
這是因為如果我們只對一個線程的內容上鎖了,另一個線程沒有對同一個Object類上鎖,兩個線程還是會各自運行各自的。
最后就是:兩個線程針對不同的 Object 類上鎖也會有線程安全問題:
public class Demo6 {static int result = 0;static Object locker = new Object();static Object locker2 = new Object();public static void add(int a){result++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}}});Thread t2 = new Thread(()->{synchronized(locker2){for (int i = 0; i < 50000; i++) {add(result);};}});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}