本篇文章主要是對JUC包下,一些併發類的源碼分析,如果想了解具體實例,請點擊
併發容器
ConcurrentHashMap
//使用了unSafe方法,通過直接操作內存的方式來保證併發處理的安全性,使用的是硬件的安全機制。
/*
* 用來返回節點數組的指定位置的節點的原子操作
*/
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
/*
* cas原子操作,在指定位置設定值
*/
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
/*
* 原子操作,在指定位置設定值
*/
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
put
public V put(K key, V value) {
/*
* onlyIfAbsent
* false:這個value一定會設置
* true:只有當這個key的value爲空的時候纔會設置
*/
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
//不允許key/value爲null,否則及時失敗
if (key == null || value == null) throw new NullPointerException();
//獲取key的hashCode
int hash = spread(key.hashCode());
//用來計算在這個節點總共有多少個元素,用來控制擴容或者轉移爲樹
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//初始化tab
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if (
//通過哈希計算出一個表中的位置因爲n是數組的長度,所以(n-1)&hash肯定不會出現數組越界
(f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如果這個位置沒有元素的話,則通過cas的方式嘗試添加,注意這個時候是沒有加鎖的
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
/*
* 如果檢測到某個節點的hash值是MOVED,則表示正在進行數組擴張的數據複製階段,
* 則當前線程也會參與去複製,通過允許多線程複製的功能,一次來減少數組的複製所帶來的性能損失
*/
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
//再次取出要存儲的位置的元素,跟前面取出來的比較
if (tabAt(tab, i) == f) {
//取出來的元素的hash值大於0,當轉換爲樹之後,hash值爲-2
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//查找到值,則覆蓋
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
//找到最後,沒找到值的,則新建對象.
if ((e = e.next) == null) {
//添加鏈表尾端
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//對紅黑樹的處理
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//鏈表節點其中個數達到8,則擴張數組或轉成樹
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//計數
addCount(1L, binCount);
return null;
}
初始化
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
//sizeCtl初始值爲0,當小於0的時候表示在別的線程在初始化表或擴展表
//則暫停當前正在執行的線程對象,並執行其他線程。
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (
/*
SIZECTL:當前內存偏移量,
sc:期望值
-1:表示要替換的值
*/
U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
//指定了大小的時候就創建指定大小的Node數組,否則創建指定大小(16)的Node數組
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
//初始化後,sizeCtl長度爲數組長度的3/4
sizeCtl = sc;
}
break;
}
}
return tab;
}
擴容
/**
* Replaces all linked nodes in bin at given index unless table is
* too small, in which case resizes instead.
* 數組長度<64,則擴容一倍
* 否則轉成樹
*/
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
//擴容
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) {
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
//把Node組成的鏈表,轉化爲TreeNode的鏈表,頭結點依然放在相同的位置
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
//把TreeNode的鏈表放入容器TreeBin中,內部將單節點樹轉換成紅黑樹
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
private final void tryPresize(int size) {
//擴容大小>=最大的一半,直接設置成最大容量
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
//返回大於輸入參數且最近的2的整數次冪的數
tableSizeFor(size + (size >>> 1) + 1);
int sc;
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
//如果數組還沒有初始化
//putAll的時候,會執行這兒
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
//SIZECTL設置-1,表示正在初始化
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
//雙重檢查
if (table == tab) {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = nt;
//sc=3/4*n
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
}
}
//擴容後的大小<=sizeCtl或者當前數組長度>容量上限,則退出
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
else if (tab == table) {
int rs = resizeStamp(n);
//表示正在擴容
if (sc < 0) {
Node<K,V>[] nt;
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
//transfer線程數+1,當前線程將加入對transfer的處理
//transfer的時候,sc表示在transfer工作的線程數
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
//沒有在初始化或擴容,則開始擴容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
}
}
}
private static final int tableSizeFor(int c) {
/*
讓cap-1再賦值給n的目的是另找到的目標值大於或等於原值。例如二進制1000,十進制數值爲8。
如果不對它減1而直接操作,將得到答案10000,即16。顯然不是結果。
減1後二進制爲111,再進行操作則會得到原來的數值1000,即8。
*/
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
//MIN_TRANSFER_STRIDE=16.控制線程數
//每個CPU最少處理16個長度的數組元素,也就是說,如果一個數組的長度只有16,那只有一個線程會對其進行擴容的複製移動操作
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
//只有第一個線程進此方法的時候,纔會初始化數組.
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
//擴容一倍數組容量
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
//這裏標記數組初始化完成,
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
/*
* 創建一個fwd節點,這個是用來控制併發的,當一個節點爲空或已經被轉移之後,就設置爲fwd節點
* 這是一個空的標誌節點
*/
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
//是否繼續向前查找的標誌位
boolean advance = true;
//在完成之前重新在掃描一遍數組,看看有沒完成的沒
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
//如果一個數組的長度只有16,只有一個線程會對其進行擴容的複製移動操作
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
//已經完成轉移
if (finishing) {
nextTable = null;
//這裏完成nextTab=>table轉換
table = nextTab;
//爲擴容後的0.75
sizeCtl = (n << 1) - (n >>> 1);
return;
}
//正在工作的線程數-1,並返回
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n; // recheck before commit
}
}
else if ((f = tabAt(tab, i)) == null)
//數組中把null的元素設置爲ForwardingNode節點(hash值爲MOVED)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
//表示已有線程正在處理
advance = true; // already processed
else {
synchronized (f) {
//雙重檢查加鎖
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
//>=0說明是node節點
if (fh >= 0) {
//爲0則表示放在擴容後數組當前索引下,否則放在n+之前位置索引下
int runBit = fh & n;
Node<K,V> lastRun = f;
/*
循環結束之後,runBit就是最後不變的hash&n的值
也就是說由lastRun節點後的hash&n的值一樣,這樣就可以直接保存,而不需要處理後面的節點
*/
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
//分別逆序存入ln或hn鏈表中
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
//存入之前的位置
setTabAt(nextTab, i, ln);
//存入改變後的位置
setTabAt(nextTab, i + n, hn);
//設置fwd,這樣其他線程執行的時候,會跳過去.
setTabAt(tab, i, fwd);
advance = true;
}
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
/*
* 在複製完樹節點之後,判斷該節點處構成的樹還有幾個節點,
* 如果≤6個的話,就轉爲一個鏈表
*/
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
//線程數+1,幫助一起轉換
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
get
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//如果first匹配key-value,則直接返回
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//遍歷查找
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
replace#remove
public V remove(Object key) {
return replaceNode(key, null, null);
}
/**
* {@inheritDoc}
*
* @throws NullPointerException if any of the arguments are null
* key映射有值,值爲oldValue,則更新
*/
public boolean replace(K key, V oldValue, V newValue) {
if (key == null || oldValue == null || newValue == null)
throw new NullPointerException();
return replaceNode(key, newValue, oldValue) != null;
}
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//空判斷
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
//正在擴容,則加入擴容隊伍
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
//雙重檢查加鎖,DCL
if (tabAt(tab, i) == f) {
//對鏈表的處理
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
//匹配key
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
//匹配value
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
//修改
if (value != null)
e.val = value;
//value=null,說明執行刪除操作
else if (pred != null)
pred.next = e.next;
//當匹配的值是first節點的處理
else
setTabAt(tab, i, e.next);
}
break;
}
//臨時保存上一個節點,便於執行刪除節點操作
pred = e;
//執行最後一個節點,則退出
if ((e = e.next) == null)
break;
}
}
//紅黑樹的處理
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
//記數-1
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
總結:
什麼時候擴容?
- 單節點容量>=8且容量<64,則擴容一倍
- 當數組中元素達到了sizeCtl的數量的時候,則會調用transfer方法來進行擴容
沒有實現對map進行加鎖來執行獨佔訪問,因爲採用了分段鎖,所以無法使用客戶端加鎖來創建新的原子操作,如若沒有則添加之內操作.
JDK1.8放棄分段鎖
段Segment繼承了重入鎖ReentrantLock,有了鎖的功能,每個鎖控制的是一段,當每個Segment越來越大時,鎖的粒度就變得有些大了。
分段鎖的優勢在於保證在操作不同段 map 的時候可以併發執行,操作同段 map 的時候,進行鎖的競爭和等待。這相對於直接對整個map同步synchronized是有優勢的。
缺點在於分成很多段時會比較浪費內存空間(不連續,碎片化); 操作map時競爭同一個分段鎖的概率非常小時,分段鎖反而會造成更新等操作的長時間等待; 當某個段很大時,分段鎖的性能會下降。
jdk1.8的map實現
和hashmap一樣,jdk 1.8中ConcurrentHashmap採用的底層數據結構爲數組+鏈表+紅黑樹的形式。數組可以擴容,鏈表可以轉化爲紅黑樹。
爲什麼不用ReentrantLock而用synchronized ?
減少內存開銷:如果使用ReentrantLock則需要節點繼承AQS來獲得同步支持,增加內存開銷,而1.8中只有頭節點需要進行同步。
內部優化:synchronized則是JVM直接支持的,JVM能夠在運行時作出相應的優化措施:鎖粗化、鎖消除、鎖自旋等等。
多個線程又是如何同步處理的呢?
- 同步處理主要是通過Synchronized和unsafe兩種方式來完成的。
- 在取得sizeCtl、某個位置的Node的時候,使用的都是unsafe的方法,來達到併發安全的目的
當需要在某個位置設置節點的時候,則會通過Synchronized的同步機制來鎖定該位置的節點。 - 在數組擴容的時候,則通過處理的步長和fwd節點來達到併發安全的目的,通過設置hash值爲MOVED
- 當把某個位置的節點複製到擴張後的table的時候,也通過Synchronized的同步機制來保證現程安全
CopyOnWriteArrayList
寫入時複製,只要正確發佈一個事實不可變對象,在訪問該對象時就不再需要進一步同步,在每次修改時,都會創建並重新發佈一個新的容器副本,從而實現可變性.
使用場景:迭代>修改,事件通知系統(註冊和註銷事件監聽器操作少於接收事件通知的操作)
//構造時,初始化容量爲0的Object數組
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
add
public boolean add(E e) {
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//創建數組副本,容量+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//更新舊的數組
setArray(newElements);
return true;
} finally {
//解鎖
lock.unlock();
}
}
remove
public E remove(int index) {
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
//獲取數組以及當前值
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
//最後一個數組索引,則直接copy
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//copy2次
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
//解鎖
lock.unlock();
}
}
get
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
set
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
//副本修改
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
//副本設置爲當前數組
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
//保證最終一致性
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
總結
應用場景:讀多寫少
如:黑名單,監聽器
讀上沒加鎖,所以支持大量併發,但涉及修改的時候,有2個數組副本,當數組過大,則造成多餘的內存消耗.
CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。之所以只能保證最終一致,是因爲每次涉及到修改都會copy一個副本然後回寫,最終的結果是一致的,但是在copy途中如果有讀操作,那麼就會造成數據不一致問題.
LinkedBlockingQueue
一個基於鏈表的阻塞隊列。此隊列按 FIFO(先進先出)排序元素
public LinkedBlockingQueue() {
//默認最大容量
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//維護雙端隊列
last = head = new Node<E>(null);
}
put
//put將指定元素插入此隊列尾部,將等待可用的空間
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
int c = -1;
//創建新節點
Node<E> node = new Node<E>(e);
//獲取put鎖
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//如果當前線程未被中斷,則獲取鎖。
putLock.lockInterruptibly();
try {
//達到上限容量,則一直等待
while (count.get() == capacity) {
notFull.await();
}
//設置值
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
//列是否有可用空間,如果有則喚醒一個等待線程
notFull.signal();
} finally {
//釋放鎖
putLock.unlock();
}
// 如果隊列中有一條數據,喚醒消費線程進行消費
if (c == 0)
signalNotEmpty();
}
offer
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
//等於最大容量,則返回,而不阻塞
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
//因爲不阻塞,所以直接獲取鎖
putLock.lock();
try {
//再次檢查容量大小,然後直接添加,隨後喚醒一個等待線程
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
// 如果隊列中有一條數據,喚醒消費線程進行消費
if (c == 0)
signalNotEmpty();
return c >= 0;
}
阻塞時間的offer
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//獲取中斷鎖
putLock.lockInterruptibly();
try {
//等於最大容量,則一直循環
while (count.get() == capacity) {
//超過超時時間則返回
if (nanos <= 0)
return false;
//當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<E>(e));
c = count.getAndIncrement();
//通知信號
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
// 如果隊列中有一條數據,喚醒消費線程進行消費
if (c == 0)
signalNotEmpty();
return true;
}
take
//獲取並移除此隊列的頭部,在元素變得可用之前一直等待
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//中斷點
takeLock.lockInterruptibly();
try {
//隊列爲空,阻塞等待
while (count.get() == 0) {
notEmpty.await();
}
//獲取值
x = dequeue();
c = count.getAndDecrement();
// 隊列中還有元素,喚醒下一個消費線程進行消費
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
// 之前隊列是滿的,則喚醒生產線程進行添加元素
if (c == capacity)
signalNotFull();
return x;
}
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
//head默認root的value是null
Node<E> h = head;
Node<E> first = h.next;
// head節點原來指向的節點的next指向自己,等待下次gc回收
h.next = h; // help GC
// head節點指向下一個節點
head = first;
//獲取新的head的value
E x = first.item;
//新的head設置null
first.item = null;
return x;
}
poll
public E poll() {
final AtomicInteger count = this.count;
//容量爲0,直接返回
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
peek
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
remove
public boolean remove(Object o) {
//爲null,直接返回
if (o == null) return false;
//put和take鎖,就暫時不能新增或修改
fullyLock();
try {
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
//匹配到值,則刪除
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
return false;
} finally {
//釋放2個鎖
fullyUnlock();
}
}
void unlink(Node<E> p, Node<E> trail) {
// assert isFullyLocked();
// p.next is not changed, to allow iterators that are
// traversing p to maintain their weak-consistency guarantee.
p.item = null;
//在迭代的時候,如果p.next爲null,則會造成異常.所以這裏沒設置null
trail.next = p.next;
if (last == p)
last = trail;
// 如果刪除之前元素是滿的,刪除之後就有空間了,喚醒生產線程放入元素
if (count.getAndDecrement() == capacity)
notFull.signal();
}
迭代器
當執行迭代器的nextNode的時候,如果同時發現有執行take操作,因爲當前head.next指向了自己,
private Node<E> nextNode(Node<E> p) {
for (;;) {
Node<E> s = p.next;
//take時,head.next=head,則直接返回當前head的下一個節點
if (s == p)
return head.next;
if (s == null || s.item != null)
return s;
p = s;
}
}
總結
底層阻塞隊列FIFO.內部由兩個ReentrantLock來實現出入隊列的線程安全,由各自的Condition對象的await和signal來實現等待和喚醒功能。
默認容量無界,且底層鏈表,所以執行插入和刪除效率比較高.且2把鎖維護新增刪除,所以併發有所提高.
ArrayBlockingQueue
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
//一把鎖管理
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
put
public void put(E e) throws InterruptedException {
//value不允許爲null
checkNotNull(e);
final ReentrantLock lock = this.lock;
//中斷鎖
lock.lockInterruptibly();
try {
//當前count超過生產的容量上限,則等待
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
//釋放鎖
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
//當前個數超過下標,則回滾爲0下標
if (++putIndex == items.length)
putIndex = 0;
//當前可進行消息個數+1
count++;
//喚醒消費線程進行消費
notEmpty.signal();
}
offer
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
//當前消息個數超過上限,則直接返回
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//當前沒有消息,則等待
while (count == 0)
notEmpty.await();
//消費消息
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//獲取消息
E x = (E) items[takeIndex];
//清空消息
items[takeIndex] = null;
//當前消費的索引爲數組長度,則從0開始消費消息
if (++takeIndex == items.length)
takeIndex = 0;
count--;
//迭代器衝突處理
if (itrs != null)
itrs.elementDequeued();
//喚醒生產線程,生產消息
notFull.signal();
return x;
}
void elementDequeued() {
// assert lock.getHoldCount() == 1;
//消費個數爲0,則清空迭代器值
if (count == 0)
queueIsEmpty();
else if (takeIndex == 0)
//消費的索引回滾爲0處理
takeIndexWrapped();
}
remove
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
int i = takeIndex;
//遍歷takeIndex ~ putIndex之間的數據,如果涉及到邊界問題,則從0開始查找
do {
//匹配則刪除
if (o.equals(items[i])) {
removeAt(i);
return true;
}
//邊界檢測.
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
void removeAt(final int removeIndex) {
// assert lock.getHoldCount() == 1;
// assert items[removeIndex] != null;
// assert removeIndex >= 0 && removeIndex < items.length;
final Object[] items = this.items;
//如果刪除的消息和目前正take索引相同
if (removeIndex == takeIndex) {
// removing front item; just advance
//清空當前take消息
items[takeIndex] = null;
//takeIndex偏移到下一個
if (++takeIndex == items.length)
takeIndex = 0;
count--;
//迭代器衝突處理
if (itrs != null)
itrs.elementDequeued();
} else {
// an "interior" remove
// slide over all others up through putIndex.
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
//達到數組上線,索引設爲0,開始遍歷
if (next == items.length)
next = 0;
//未到邊界,則偏移到下一個數組值
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
//到了邊界,設置當前items[putIndex]值爲null,且更新putIndex
items[i] = null;
this.putIndex = i;
break;
}
}
//當前消息個數-1
count--;
//迭代器衝突處理
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
總結
有界阻塞隊列。此隊列按 FIFO(先進先出)原則對元素進行排序。
初始化必須設置容量,value不允許爲空
全局一個鎖處理,相對併發比較低.
因爲底層數組,所以修改查詢快.