在本章節中采用實例+圖片的方式,以一個學習者的姿態進行描述問題+解決問題,更加清晰明了,以及過程中會發問的問題都會一一進行呈現
目錄
- 線程安全
- 演示線程不安全情況
- 圖片解釋:
- 將上述代碼進行修改【從并行轉化成穿行的方式】
- 不會出現問題的可能
- 埋坑問題
- 總結(線程安全問題產生原因)
- 如何解決線程安全問題
- 1.根本原因:
- 2.多線程同時修改一個變量
- 3.修改操作,不是原子的
- 注意:
線程安全
概念:一段代碼在多線程并發執行的情況下,出現bug的情況(實際結果與預期結果不符合),預期結果:一般是由別人來預期,此時這種情況是“線程不安全”
演示線程不安全情況
eg:(如果我們需要計算一個20000;兩個線程每個線程執行10000次看其的一個情況)
public class Test {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 10000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 10000; i++) {count++;}});t1.start();t2.start();Thread.sleep(1000);System.out.println(count);}
}
最終運行的結果為:
對代碼進行解釋:
其中
Thread t1 = new Thread(()->{
for (int i = 0; i < 10000; i++) {
count++;
}
});
對應的是3個cpu的操作:
- load:將內存中的count加載到寄存器上
- add:把寄存器中的內容進行+1;
- save:把寄存器當中的內容保留在內存上
所以最終會出現這樣的情況
圖片解釋:
將上述代碼進行修改【從并行轉化成穿行的方式】
//只需要使用join方法就可以將并行執行的方式改為串行的方式
public class Test {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 10000; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 10000; i++) {count++;}});t1.start();t1.join();t2.start();t2.join();Thread.sleep(1000);System.out.println(count);}
}
最終的結果為:20000
此時上述的反應就被稱為——“線程不安全”
不會出現問題的可能
- 如果線程重復的次數少:其運行的速度非常快,會出現一個線程執行完了,另一個線程還沒有開始執行——結果就是正確的
eg:
public class Test {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 10; i++) {count++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 10; i++) {count++;}});t1.start();//t1.join();t2.start();//t2.join();Thread.sleep(1000);System.out.println(count);}
}
此時的結果就是正確的;
由于他的速度非常快,在狀態轉化的過程當中前一個線程就已經完成了操作
埋坑問題
- 在上述的代碼當中是否可能出現最終打印的結果<5000
答:可能會出現
解釋圖片:
在上面的情況中會出現最終打印的1 - 那由上面問題 ,是否最終在我們之前寫的代碼中出現打印1的情況
答:這個是不太可能的
因為執行的次序是搶占資源的方式,在這個過程中,很少可能會出現4999都只由一個線程執行,然后由兩一個線程進行收尾
總結(線程安全問題產生原因)
- 根本原因:操作系統對線程的調度是隨機的,搶占式執行的方式
- 多個線程同時修改同一個變量
以下是不會出現問題的情況:
一個線程修改一個變量
多個線程不同時修改同一個變量
多個線程修改不同變量
多個線程讀取同一變量 - 修改操作不是原子的
進行解釋:如果修改操作只對應一個cpu質量——此時原子的(例如沒有多線程的時候,java當中的main線程就不會發生任何的問題)
如何解決線程安全問題
將他產生的原因盡行打破
1.根本原因:
這個是操作系統底層的設定,不能進行改變
或者自己寫一個操作系統:兩大難點1》技術上會非常難2》推廣上會更加難
2.多線程同時修改一個變量
解決方法:調整代碼結構【但是這種方法并不是通用的】
3.修改操作,不是原子的
這個是java當中解決線程安全問題,最主要的解決方案
加鎖
關鍵字:synchronized
synchronized(加鎖對象){——加鎖操作
執行相關代碼
}——解鎖操作
加鎖操作,不是將整個線程鎖死在cpu上,禁止這個線程被其他的調度走,但是禁止其他線程重新加這個鎖,避免其線程成的操作
java當中可以使用這個關鍵字修飾任何對象
只有兩個線程針對同一個對象加鎖操作,才會產生互斥效果
鎖對象并不會影響這個對象其他方面的使用
結果展示:
public class Test2 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Object o = new Object();Thread t1 = new Thread(()->{synchronized (o) {for (int i = 0; i < 10; i++) {count++;}}});Thread t2 = new Thread(()->{synchronized (o) {for (int i = 0; i < 10; i++) {count++;}}});t1.start();//t1.join();t2.start();//t2.join();Thread.sleep(1000);System.out.println(count);}
}
上述代碼的最終的結果就可以被正確打印
對代碼進行的解釋:
注意:
在此過程中,只有針對同一個對象加鎖才會有效果
在一般情況下:synchronized是對this進行加鎖
當synchronized修飾static修飾的變量時,相當于針對類對象盡心的加鎖操作
下一張會講到死鎖的問題,不要走開哦!!!!