樂觀鎖與悲觀鎖總結
656
2025-04-04
摘要
并發編程世界里,由于CPU緩存導致的可見性問題,線程切換導致的原子性問題,以及編譯器重排序導致的有序性問題是并發編程Bug的根源。
正文
可見性
一個線程對共享變量的修改。另外一個線程能夠立刻看到,我們稱之為可見性。共享變量指的是存放在堆內存,由所有線程所共享的變量。比如:實例變量,靜態變量。
如圖所示:
共享變量V可以由線程A和線程B同時操作,線程A和B首先從各自的CPU緩存或者寄存器中讀取數值,然后由CPU的寄存器寫入內存中。
public class LongTest { private static long atest = 0L; public void countTest() { for (int i = 0; i < 10000; i++) { atest = atest + 1; } } public static void main(String[] args) throws InterruptedException { final LongTest longTest = new LongTest(); Thread threadA = new Thread(new Runnable() { public void run() { longTest.countTest(); } }); Thread threadB = new Thread(new Runnable() { public void run() { longTest.countTest(); } }); threadA.start(); threadB.start(); threadA.join(); threadB.join(); System.out.println("*******獲得到的atest值為=" + atest); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
如上程序,運行之后我們會發現 atest 值永遠都不會到達20000,而是在10000-20000之間的隨機數。
原因分析:假設線程A和線程B同時執行 atest = atest + 1;, 線程A 讀取到的原值是0,執行+1操作之后,得到新值1,同樣的,線程B也是讀取到的原值0,然后執行+1操作,得到新值1。這樣就永遠得不到結果2。
類推的話,循環執行10000次也是同理,線程A執行+1操作時不能及時獲得線程B已經寫入的值,故導致值永遠不可能達到20000。
原子性
由于一條高級語句在CPU中可能會分成若干條指令來執行,每條指令執行完之后就有可能會發生線程切換。故線程切換造成的原子性問題。
例如: count=count+1 共有三個指令
指令一: 將count值從內存加載到到CPU的寄存器中
指令二:在寄存器中+1操作
指令三 :將新值寫入到內存中(由于緩存機制,寫入的可能是CPU的緩存而不是內存)
操作系統做任務切換可以發生在任何一條CPU指令執行完,是CPU指令執行完。
我們將一個或者多個操作在CPU執行過程中不被中斷的特性稱之為原子性。
有序性
編譯器重排序導致的有序性問題:
例如:雙重加鎖中的:
public class SingletonDemo { private static SingletonDemo instance = null; private SingletonDemo(){ } static SingletonDemo getSingletonDemo() { if (instance == null) { synchronized (SingletonDemo.class) { if (instance == null) { instance = new SingletonDemo(); //6 } } } return instance; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
這里會有有序性問題:
問題主要出在了 new SingletonDemo() 這一步
因為instance = new SingletonDemo();主要有三個指令
分配內存空間M
在內存M上初始化對象
然后M的地址賦值給instance變量
正常順序是1-2-3
但是CPU重排序之后執行順序可能變成了 1-3-2
步驟如下:
A首先進入synchronized,由于instance為null,所以它執行instance = new SingletonDemo();
然后線程 A執行1->JVM先畫出了一些分配給SingletonDemo實例的空白內存,并賦值給instance
在還沒有進行第三步(將instance引用指向內存空間)的時候,恰好發生了線程切換 ,切換到了線程B上,
如果此時線程B也執行getSingletonDemo()方法,那么線程B 在執行第一個判斷是會發現instance!=null,所以直接返回了instance,而此時的instance是沒有初始化的。
總結
并發編程中主要的問題就是可見性問題, 原子性問題,有序性問題。本文介紹了這三種問題的發生原因,以及發生的場景。
Java 任務調度
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。