_**Java虛擬機規范定義的許多規則中的一條:所有對基本類型的操作除了某些對long類型和double類型的操作之外,都是原子級的;**_
> 當線程把主存中的 long/double類型的值讀到線程內存中時,可能是兩次32位值的寫操作,顯而易見,如果幾個線程同時操作,那么就可能會出現高低2個32位值出錯的情況發生。即long,double高低位問題,非線程安全
舉例說明:
即如有一個long類型的field字段,某個線程正在執行 field = 123L ,而同時有另一個線程正在執行 field = 456L,這樣的指定操作之后field的值會是什么,是無法保證的。也許是123L,也可能是456L,或許是0L,甚至還可能是31415926L
### JVM內存模型中定義了8種原子操作
1. lock:將一個變量標識為被一個線程獨占狀態
2. unclock:將一個變量從獨占狀態釋放出來,釋放后的變量才可以被其他線程鎖定
3. read:將一個變量的值從主內存傳輸到工作內存中,以便隨后的load操作
4. load:把read操作從主內存中得到的變量值放入工作內存的變量的副本中
5. use:把工作內存中的一個變量的值傳給執行引擎,每當虛擬機遇到一個使用到變量的指令時都會使用該指令
6. assign:把一個從執行引擎接收到的值賦給工作內存中的變量,每當虛擬機遇到一個給變量賦值的指令時,都要使用該操作
7. store:把工作內存中的一個變量的值傳遞給主內存,以便隨后的write操作
8. write:把store操作從工作內存中得到的變量的值寫到主內存中的變量
對于32位操作系統來說單次次操作能處理的最長長度為32bit,而long類型8字節64bit,所以對long的讀寫都要兩條指令才能完成\(即每次讀寫64bit中的32bit\);如果JVM要保證long和double讀寫的原子性,勢必要做額外的處理
```
public class LongAtomTest implements Runnable {
private static long field = 0;
private volatile long value;
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
public LongAtomTest(long value) {
this.setValue(value);
}
@Override
public void run() {
int i = 0;
while (i < 100000) {
LongAtomTest.field = this.getValue();
i++;
long temp = LongAtomTest.field;
if (temp != 1L && temp != -1L) {
System.out.println("出現錯誤結果" + temp);
System.exit(0);
}
}
System.out.println("運行正確");
}
public static void main(String[] args) throws InterruptedException {
// 獲取并打印當前JVM是32位還是64位的
String arch = System.getProperty("sun.arch.data.model");
System.out.println(arch+"-bit");
LongAtomTest t1 = new LongAtomTest(1);
LongAtomTest t2 = new LongAtomTest(-1);
Thread T1 = new Thread(t1);
Thread T2 = new Thread(t2);
T1.start();
T2.start();
T1.join();
T2.join();
}
}
```
以上代碼在32位JVM上和64位JVM上運行結果將不一致
從程序得到的結果來看,32位的HotSpot沒有把long和double的讀寫實現為原子操作。 在讀寫的時候,分成兩次操作,每次讀寫32位。因為采用了這種策略,所以64位的long和double的讀與寫都不是原子操作
### 結論
* 對于64位的long和double,如果沒有被volatile修飾,那么對其操作可以不是原子的。在操作的時候,可以分成兩步,每次對32位操作;
* 如果使用volatile修飾long和double,那么其讀寫都是原子操作;
* 在實現JVM時,可以自由選擇是否把讀寫long和double作為原子操作;
* java中對于long和double類型的寫操作不是原子操作,而是分成了兩個32位的寫操作。讀操作是否也分成了兩個32位的讀呢?在JSR-133之前的規范中,讀也是分成了兩個32位的讀,但是從JSR-133規范開始,即JDK5開始,讀操作也都具有原子性;
* java中對于其他類型的讀寫操作都是原子操作\(除long和double類型以外\);
* 對于引用類型的讀寫操作都是原子操作,無論引用類型的實際類型是32位的值還是64位的值;
* 對于long類型的不恰當操作可能讀取到從未出現過的值。而對于int的不恰當操作則可能讀取到舊的值;
- 簡介
- 概述
- 進程vs線程
- 資源限制
- 有關并行的兩個定律
- 線程同步和阻塞
- 線程阻塞
- 線程的特性
- 守護線程
- 線程異常
- Thread
- 線程狀態
- 線程中斷
- wait¬ify
- suspend&resume
- join&yield
- notify¬ifyAll
- Thread.sleep
- 線程任務
- Runnable
- Callable
- Future模式
- FutureTask
- 線程實現方式
- 內核線程實現
- 用戶線程實現
- 混合實現
- Java線程的實現
- java與協程
- 纖程-Fiber
- 線程調度
- 多線程協作方式
- 阻塞
- 放棄
- 休眠
- 連接線程
- 線程估算公式
- 線程活躍性
- 死鎖
- 線程安全性
- 對象的發布與逸出
- 構造方法溢出
- 線程封閉
- 對象的可變性
- 原子性
- 原子操作
- CPU原子操作原理
- 總線鎖
- 緩存鎖
- JAVA如何實現原子操作
- long和double讀寫操作原子性
- Adder和Accumulator
- 線程性能
- 同步工具類
- 閉鎖
- CountDownLatch
- FutureTask
- 信號量
- 柵欄
- CyclicBarrier
- Exchanger
- 并發編程
- volatile
- synchronized
- 無鎖
- 偏向鎖
- 輕量級鎖
- 鎖的優缺點對比
- 鎖升級
- 鎖消除
- Monitor
- synchronized語法
- Mutex Lock
- synchronized實踐問題
- synchronized&ReentrantLock
- Lock
- ReentrantLock
- Condition
- 讀寫鎖
- ReadWriteLock
- StampedLock
- 線程池
- Executor
- ExecutorService
- Executors
- ThreadPoolExecutor
- RejectedExecutionHandler
- ThreadFactory
- 線程池大小公式
- 動態調整線程池大小
- Fork/Join框架
- ForkJoinPool
- CompletableFuture
- JUC并發工具包
- LockSupport
- 延時任務與周期任務
- Timer
- TimerTask
- 異構任務并行化
- CompletionService
- volatile和synchronized比較
- 鎖優化
- 鎖相關概念
- 悲觀鎖(排它鎖)
- 樂觀鎖
- 自旋鎖
- 樂觀鎖vs悲觀鎖
- JVM鎖優化-鎖消除
- ThreadLocal
- InheritableThreadLocal
- TransmittableThreadLocal
- ThreadLocalRandom
- 無鎖
- AtomicInteger
- Unsafe
- AtomicReference
- AtomicStampedReference
- AtomicIntegerArray
- AtomicIntegerFieldUpdater
- 無鎖Vector
- LongAdder
- LongAccumulator
- 常見鎖類型
- 悲觀鎖&獨占鎖
- 樂觀鎖
- 樂觀鎖vs悲觀鎖
- 自旋鎖vs適應性自旋鎖
- 公平鎖vs非公平鎖
- 可重入鎖vs非可重入鎖
- 獨享鎖vs共享鎖
- 互斥鎖
- CAS
- AQS介紹
- AQS深入剖析
- AQS框架
- AQS核心思想
- AQS數據結構
- 同步狀態State
- ReentrantLock vs AQS
- AQS與ReentrantLock的關聯
- ReentrantLock具體實現
- 線程加入等待隊列
- 等待隊列中線程出隊列時機
- 如何解鎖
- 中斷恢復后的執行流程
- ReentrantLock的可重入應用
- JUC中的應用場景
- 自定義同步工具
- CLH鎖
- 并發框架
- Akka
- Disruptor-無鎖緩存框架
- 常見面試題
- 兩個線程交替打印A和B
- 附錄