原子特性: 原子是最小的粒子,不可再分
這並不是一個化學課,而是巧妙的借用了化學上的一個概念,即原子是最小的粒子,不可再分;原子操作也是不能再分的操作;
爲了能把這個講明白,下文基本都是大白話,其實Java本來並不是很難,而是總有一些人喜歡把簡單的概念給複雜化。小編不喜歡
那種說辭,所以儘量簡單易懂。如有問題,歡迎提出問題。共同交流進步,最後謝謝你的閱讀。
舉例說明原子操作重要性
在很多場景中我們需要我們的操作是原子特性的,如果我們寫的程序都是單線程的,其實我們沒必要考慮原子操作。但是假如
我們寫多線程操作,或者是在Web服務中來更新對象屬性,那麼就必須要來考慮原子操作問題了。
舉一個🌰例子A:
int a = 1;
可以看到程序對變量 a
操作,其實是有多個步驟進行的。在單線程環境下基本不會發生任何問題
舉一個🌰例子B(單線程操作):
public class Tester {
private static Integer a = 1;
private static AtomicInteger aa = new AtomicInteger(1);
private static void restore() {
a = 1;
aa = new AtomicInteger(1);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
test("第" + i + "次");
restore();
}
}
private static void test(String str) {
for (int i = 1; i <= 1000; i++) {
new Thread(() -> a = a + 1).start();
new Thread(() -> aa.addAndGet(1)).start();
}
System.out.print(str + "常規操作a=" + a);
System.out.println(" <===> "+str+"原子操作操作aa=" + aa);
}
}
規律:
/**
* i i+1
* 1: a = 1 + 1 = 2
* 2: a = 2 + 1 = 3
* 3: a = 3 + 1 = 4
* 4: a = 4 + 1 = 5
* 5: a = 5 + 1 = 6
* 6: a = 6 + 1 = 7
* 7: a = 7 + 1 = 8
* 8: a = 8 + 1 = 9
* 9: a = 9 + 1 = 10
* 10:a = 10 + 1 = 11
*/
如上面代碼變量a是基本類型,變量aa是原子類型,正常情況對a或者aa進行1000次操作結果都應該是
1001
。正常情況我們可以理解是單線程操作。結果也是沒有問題的。
舉一個🌰例子C(多線程操作):
public class Tester {
private static Integer a = 1;
private static AtomicInteger aa = new AtomicInteger(1);
private static void restore() {
a = 1;
aa = new AtomicInteger(1);
}
public static void main(String[] args) throws Exception {
for (int i = 0; i < 10; i++) {
test("第" + i + "次");
restore();
}
}
private static void test(String str) throws Exception {
for (int i = 1; i <= 100; i++) {
new Thread(() -> a = a + 1).start();
new Thread(() -> a = a + 1).start();
new Thread(() -> aa.addAndGet(1)).start();
new Thread(() -> aa.addAndGet(1)).start();
Thread.sleep(1);
}
System.out.print(str + "常規操作a=" + a);
System.out.println(" <===> " + str + "原子操作操作aa=" + aa);
}
}
規律:
/**
* i 2 * i + 1
* 1: a = 1 + 1 + 1 = 3
* 2: a = 3 + 1 + 1 = 5
* 3: a = 5 + 1 + 1 = 7
* 4: a = 7 + 1 + 1 = 9
* 5: 11
* 6: 13
* 7: 15
* 8: 17
* 9: 19
* 10: 21
*/
多線程環境下操作會不會有問題呢? 出現了問題。我們看到使用常規操作的a變量出現了數據不一致情況。
實際上當循環的次數越多,出現錯誤的機率就越大,如下我們循環了1000次。
問題分析
我們思考爲什麼基本類型進行多線程操作時候回出現這種情況呢? 其實問題答案最開始已經說了。 我們通過這張圖
就可以找到原因。
對變量的每次操作其實都有3個步驟
- 讀取變量值
- 變量值操作
- 變量重新賦值。
我們模擬一下錯誤的原因。
當A線程讀取a=1,並對1+1。但是還未對變量重新賦值a=2的時候,
B線程也讀取了A還未賦值的變量,此時變量還是1,那麼B線程因爲讀取了還未更新的數據,所以也做1+1的操作。然後B對a
重新賦值了此時a=2,是B賦值的。這個時候A因爲已經執行完了前兩個步驟,最後也重新賦值了a=2。
這樣數據就更新丟了。這就是因爲數據更新不是原子性從而導致的問題。
因爲數據更新丟了,所以出現了。
如何解決這種問題
如何解決這種問題,其實很簡單隻要我們保證我們的操作是原子操作即可,簡單來說就是講更新的三個步驟合併成一個步驟即可,在Java中JDK已經爲我們提供了很多的
原子操作每一個基本類型都對應一個原子操作。
原子基礎類
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hrW44BIR-1589381172662)(https://img.springlearn.cn/blog/learn_1589378016000.png)]
原子基礎類API
原子數組類
原子更新數組API
原子引用類
注意:
想要原子的更新字段,需要兩個步驟:
1.每次使用的時候必須使用靜態方法newUpdater()創建一個更新器,並且需要設置想要更新的類和屬性
2.更新類的字段(屬性)必須使用public volatile修飾符
最後我們看一下原子操作的原理
最後求關注,求訂閱,感謝您的閱讀,本文由 程序猿升級課 版權所有。如若轉載,請註明出處:程序猿升級課(https://blog.springlearn.cn/)