在Java中,線程安全性是指:當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些進程將如何交替進行,並且在主調代碼中不需要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的。
線程安全性體現在三個方面:原子性、可見性、有序性。
一. 原子性
原子性提供了互斥訪問,同一時刻只能有一個線程來對它進行操作。
保證原子性有兩種方式:
(1)Atomic包
- AtomicXXX:CAS、Unsafe.compareAndSwapInt
- AtomicLong、LongAdder(兩者區別:https://blog.csdn.net/yao123long/article/details/63683991)
- AtomicReference、AtomicReferenceFieldUpdater(添加的類屬性需要使用volatile修飾)
- AtomicStampReference:解決CAS的ABA問題
(2)鎖
- synchronized:依賴JVM,修飾代碼塊、方法、靜態方法、類(子類繼承父類後,父類的synchronized修飾的方法不起作用(synchronized不屬於方法聲明的一部分),子類需要自己顯式聲明synchronized關鍵字。)
- Lock:依賴特殊的CPU指令,代碼實現,ReentrantLock
二. 可見性
可見性指一個線程對主內存的修改可以及時的被其他線程觀察到。
volatile:通過加入內存屏障和禁止重排序優化來實現。(不保證原子性)
- 對volatile變量寫操作時,會在寫操作後加入一條store屏障指令,將本地內存中的共享變量值刷新到主內存;
- 對volatile變量讀操作時,會在讀操作前加入一條load屏障指令,從主內存中讀取共享變量。
使用volatile必須具備2個條件:①對變量的寫操作不依賴當前值;②該變量沒有包含在具有其他變量的不變式中。
三. 有序性
有序性指一個線程觀察其他線程中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序。
happens-before原則保證指令不能重排,共有8條原則:
- 程序順序原則:一個線程內保證語義的串行性。
- volatile規則:volatile變量的寫,先發生於讀,這保證了volatile變量的可見性。
- 鎖規則:解鎖(unlock)必然發生在隨後的加鎖(lock)前。
- 傳遞性:A先於B,B先於C,那麼A必然先於C。
- 線程的start()方法先於它的每一個動作。
- 線程的所有操作先於線程的終結(Thread.join())。
- 線程的中斷(interrupt())先於被中斷線程的代碼。
- 對象的構造函數執行、結束先於finalize()方法。