併發程式設計-進階
變數與執行緒安全
volatile關鍵字
1.強制執行緒到共享記憶體中讀取資料,而不從執行緒工作記憶體中讀取,從而使變數在多個執行緒間可見。
2.volatile無法保證原子性,volatile屬於輕量級的同步,效能比synchronized強很多(不加鎖),但是隻
保證執行緒見的可見性,並不能替代synchronized的同步功能,netty框架中大量使用了volatile
加入兩個執行緒都修改這個volatile變數對其++,可能最後變數就只是1而已,而不是2,因為兩個執行緒讀到的時候都是0
volatile與static的關係
Static保證唯一性, 不保證一致性,多個例項共享一個靜態變數。(複製一份到執行緒記憶體再寫入)
Volatile保證一致性,不保證唯一性,多個例項有多個volatile變數。(直接修改變數值)
Automatic的原子性
使用AtomicInteger等原子類可以保證共享變數的原子性
但是Atomic類不能保證成員方法的原子性
Actomic類採用了CAS這種非鎖機制
什麼是CAS?
引入版本號機制,每次修改資料後版本號+1,每次修改資料時對比版本號是否相同?
版本號不一致的時候,再讀取當前版本號資料進行操作,再對比版本號,死迴圈,直到版本號相同為止,才能成功修改資料
所以叫做compare and swap check and swap 比如檢視Automatic原始碼就可以知道
public final int addAndGet(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta) + delta; } public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; } 底層使用的是CAS原理,沒有使用synchronized同步方法,非鎖機制,所以資料是原子性操作,計算永遠不會出錯! 這裡的while程式碼就是個死迴圈
CAS詳解
1.JDK提供的非阻塞原子操作,通過硬體保證了比較、更新操作的原子性
2.JDK的Unsafe類提供了一系列的compareAndSwap*方法來支援CAS操作
ABA問題:
- 如果程式按照1~5的順序執行,依然是成功的,然而執行緒1修改時x的值時其實已經從x= A =>B =>A。
- 由於變數的值產生了環形轉換,從A變為B又變回了A。如果不存在環形轉換也就不存在ABA問題。
ABA解決方法:
1.給變數分配時間戳、版本來解決ABA問題
2.JDK中使用java.util.concurrent.atomic.AtomicStampedReference類給每個變數的狀態都分配一個時間
戳,避免ABA問題產生。
/**
* AtomicStampedReference 來解決CAS的ABA問題 版本號機制stamp
*/
private static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(100, 0);
也可以利用這個類實現自己的CAS操作
檢視原始碼
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
其實就是有pair建構函式,採用單例模式,維護pair可見性。
ThreadLocal原理
使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每
一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本
換句話說,對threadLocal的操作都是當前執行緒的資料操作,都是把賦值的資料儲存到當前執行緒的操作
ThreadLocal原始碼解析
-
Thread類中的threadLocals、inheritableThreadLocals成員變數為ThreadLocal.ThreadLocalMap物件
-
Map的key值是ThreadLocal物件本身,檢視set、set、remove方法
-
Thread無法解決繼承問題,而InheritableThreadLocal可以
-
InheritableThreadLocal繼承自ThreadLocal
-
InheritableThreadLocal可以幫助我們做鏈路追蹤,多執行緒之間讀取副本,父子執行緒之間複製傳遞
原始碼解析:
InheritableThreadLocal在初始化的時候,inheritableThreadLocals會判斷父執行緒的inheritableThreadLocals是否為空,如果不是空,那麼InheritableThreadLocal會將自己初始化為父執行緒的值
Unsafe類
AtomicXXX類大量採用Unsafe類完成底層操作
直接操作記憶體,不安全
通過反射模式可以突破Unsafe類的安全限制
地址類操作
\1. objectFieldOffset 獲取欄位偏移地址
\2. staticFieldOffset 獲取靜態欄位偏移地址
\3. arrayBaseOffset 獲取陣列中第一個元素的地址
\4. arrayIndexScale 獲取陣列中一個元素佔用的位元組
get、put
\1. getInt、getLong、getBoolean、getChar、getFloat、getByte、getDouble、getObject 獲取欄位值
\2. putInt、putLong、putBoolean、putChar、putFloat、putByte、putDouble、putObject 設定欄位值
\3. 直接操作記憶體地址
\4. 通過物件記憶體地址操作
volatile類操作
1.getIntVolatile、getLongVolatile、getBooleanVolatile、getObjectVolatile、getByteVolatile、
getShortVolatile、getCharVolatile、getFloatVolatile、getDoubleVolatile
獲取volatile欄位值,保證可見性
2.putIntVolatile、putLongVolatile、putBooleanVolatile、putObjectVolatile、putByteVolatile、
putShortVolatile、putCharVolatile、putFloatVolatile、putDoubleVolatile
設定volatile欄位值,保證可見性
and類操作
\1. putOrderedInt、 putOrderedLong、 putOrderedObject 保證順序性、具有lazy特性、不保證可見性
\2. getAndSetInt、getAndSetLong、getAndSetObject 自旋操作、先獲取後設置
\3. getAndAddInt、getAndAddLong 自旋操作、先獲取後設置
\4. compareAndSwapInt、compareAndSwapLong、compareAndSwapObject CAS相關操作
記憶體操作
\1. public native long allocateMemory(long bytes); 分配記憶體
\2. public native long reallocateMemory(long address, long bytes); 重新分配記憶體
\3. public native void setMemory(Object o, long offset, long bytes, byte value);
\4. public void setMemory(long address, long bytes, byte value) 初始化記憶體
\5. public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset,
long bytes);
\6. public void copyMemory(long srcAddress, long destAddress, long bytes) 複製記憶體
\7. public native void freeMemory(long address);
記憶體操作
\1. public native long allocateMemory(long bytes); 分配記憶體
\2. public native long reallocateMemory(long address, long bytes); 重新分配記憶體
\3. public native void setMemory(Object o, long offset, long bytes, byte value);
\4. public void setMemory(long address, long bytes, byte value) 初始化記憶體
\5. public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset,
long bytes);
\6. public void copyMemory(long srcAddress, long destAddress, long bytes) 複製記憶體
\7. public native void freeMemory(long address)
執行緒排程
\1. public native void park(boolean isAbsolute, long time); 掛起執行緒
\2. public native void unpark(Object thread); 喚醒執行緒
\3. 需要注意執行緒的interrupt方法同樣能喚醒執行緒,但是不報錯
\4. java.util.concurrent.locks.LockSupport使用unsafe實現
記憶體屏障
\1. public native void loadFence(); 保證在這個屏障之前的所有讀操作都已經完成
\2. public native void storeFence(); 保證在這個屏障之前的所有寫操作都已經完成
\3. public native void fullFence(); 保證在這個屏障之前的所有讀寫操作都已經完成
類載入
\1. public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader,
ProtectionDomain protectionDomain); 方法定義一個類,用於動態地建立類。
\2. public native Class** defineAnonymousClass**(**Class** hostClass, byte[] data, Object[]
cpPatches);用於動態的建立一個匿名內部類。
\3. public native Object allocateInstance(Class<?> cls) throws InstantiationException;方法用於建立一
個類的例項,但是不會呼叫這個例項的構造方法,如果這個類還未被初始化,則初始化這個類。
\4. public native boolean shouldBeInitialized(Class<?> c);方法用於判斷是否需要初始化一個類。
\5. public native void ensureClassInitialized(Class<?> c);方法用於保證已經初始化過一個類。
併發容器類
同步類容器
Vector、HashTable等古老的併發容器,都是使用Collections.synchronizedXXX等工廠方法建立的,
併發狀態下只能有一個執行緒訪問容器物件,鎖住的是整個集合,存在集合中不同元素有不同執行緒的操作其實是不會影響資料的,但這裡的synchronized是把整個集合物件當做鎖了,鎖的顆粒很大。所以效能很低
* 使用以下方法被包裹的類將支援多執行緒
* Collections.synchronizedCollection(c);
* Collections.synchronizedList(list);
* Collections.synchronizedMap(m);
* Collections.synchronizedSet(s);
* Collections.synchronizedSortedMap(m);
* Collections.synchronizedSortedSet(s);
final List<String> list = Collections.synchronizedList(new ArrayList<String>());
conccurentMap類併發容器
ConcurrentHashMap替代HashMap、HashTable
ConcurrentSkipListMap替代TreeMap
ConcurrentHashMap將hash表分為16個segment,每個segment單獨進行鎖控制,從而減小了鎖的
粒度,提升了效能,因為是分鎖,所以效能高
Copy On Write容器--讀寫分離
Copy On Write容器,簡稱COW;寫時複製容器,向容器中新增元素時,先將容器進行Copy出一個新
容器,然後將元素新增到新容器中,再將原容器的引用指向新容器。併發讀的時候不需要鎖定容器,
因為原容器沒有變化,使用的是一種讀寫分離的思想。由於每次更新都會複製新容器,所以如果數
據量較大,並且更新操作頻繁則對記憶體消耗很高,建議在高併發讀的場景下使用
CopyOnWriteArraySet基於CopyOnWriteArrayList實現,其唯一的不同是在add時呼叫的是
CopyOnWriteArrayList的addIfAbsent方法, adIfAbsent方法同樣採用鎖保護,並建立一個新的大小
+1的Object陣列。遍歷當前Object陣列,如Object陣列中已有了當前元素,則直接返回,如果沒有
則放入Object陣列的尾部,並返回。從以上分析可見,CopyOnWriteArraySet在add時每次都要進
行陣列的遍歷,因此其效能會低於CopyOnWriteArrayList.
先複製舊陣列,加上新陣列,再將原物件指向該陣列
COW弱一致性問題
CopyOnWriteArrayList
CopyOnWriteArraySet
Iterator<Integer> iterator = list.iterator();
迭代器獲取的資料是此時的資料,和後續容器是否新增資料無關
併發佇列
無阻塞佇列ConcurrentLinkedQueue
ConcurrentLinkedQueue併發無阻塞佇列,BlockingQueue併發阻塞佇列,均實現自Queue介面
ConcurrentLinkedQueue無阻塞、無鎖、高效能、無界、執行緒安全,效能優於BlockingQueue、不
允許null值
有界阻塞佇列ArrayBlockingQueue
ArrayBlockingQueue:基於陣列實現的阻塞有界佇列、建立時可指定長度,內部實現維護了一個定
長陣列用於快取資料,內部沒有采用讀寫分離,寫入和讀取資料不能同時進行,不允許null值
add滿時會丟擲異常
off滿時可設定等待時間 不拋異常
put滿時會阻塞,不丟擲異常
無界阻塞佇列LinkedBlockingQueue
LinkedBlockingQueue :基於連結串列的阻塞佇列,內部維護一個連結串列儲存快取資料, 支援寫入和讀取
的併發操作, 建立時可指定長度也可以不指定,不指定時代表無界佇列, 不允許null值
無容阻塞佇列SynchronousQueue
SynchronousQueue :沒有任何容量,必須現有執行緒先從佇列中take,才能向queue中add/put資料,否
則會丟擲佇列已滿的異常。不能使用peek方法取資料,此方法底層沒有實現,會直接返回null
提供執行緒間資訊的傳送。本身並不儲存資料。
優先阻塞佇列PriorityBlockingQueue
PriorityBlockingQueue:一個無界阻塞佇列,預設初始化長度11,也可以手動指定,但是佇列會自
動擴容。資源被耗盡時導致 OutOfMemoryError。不允許使用 null元素。不允許插入不可比較的對
象(導致丟擲 ClassCastException), 加入的物件實現Comparable介面,因為資料是有優先順序的,有自定義排序
延遲阻塞佇列DelayQueue
DelayQueue:Delayed 元素的一個無界阻塞佇列,只有在延遲期滿時才能從中提取元素。該佇列
的頭部 是延遲期滿後儲存時間最長的 Delayed 元素。如果延遲都還沒有期滿,則佇列沒有頭部,
並且 poll 將返回 null。當一個元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一個小於等於
0 的值時,將發生到期。即使無法使用 take 或 poll 移除未到期的元素,也不會將這些元素作為正
常元素對待。例如,size 方法同時返回到期和未到期元素的計數。此佇列不允許使用 null 元素。內
部元素需實現Delayed介面
場景:快取到期刪除、任務超時處理、空閒連結關閉等