1. 程式人生 > 其它 >併發程式設計-進階

併發程式設計-進階

變數與執行緒安全

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. 如果程式按照1~5的順序執行,依然是成功的,然而執行緒1修改時x的值時其實已經從x= A =>B =>A。
  2. 由於變數的值產生了環形轉換,從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原始碼解析

  1. Thread類中的threadLocals、inheritableThreadLocals成員變數為ThreadLocal.ThreadLocalMap物件

  2. Map的key值是ThreadLocal物件本身,檢視set、set、remove方法

  3. Thread無法解決繼承問題,而InheritableThreadLocal可以

  4. InheritableThreadLocal繼承自ThreadLocal

  5. InheritableThreadLocal可以幫助我們做鏈路追蹤,多執行緒之間讀取副本,父子執行緒之間複製傳遞

原始碼解析:

InheritableThreadLocal在初始化的時候,inheritableThreadLocals會判斷父執行緒的inheritableThreadLocals是否為空,如果不是空,那麼InheritableThreadLocal會將自己初始化為父執行緒的值

Unsafe類

AtomicXXX類大量採用Unsafe類完成底層操作

直接操作記憶體,不安全

通過反射模式可以突破Unsafe類的安全限制

地址類操作

\1. objectFieldOffset 獲取欄位偏移地址

\2. staticFieldOffset 獲取靜態欄位偏移地址

\3. arrayBaseOffset 獲取陣列中第一個元素的地址

\4. arrayIndexScale 獲取陣列中一個元素佔用的位元組

getput

\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介面

場景:快取到期刪除、任務超時處理、空閒連結關閉等