02Hikari源碼解析之ConcurrentBag、FastList分析
一、介紹
concurrentBag 是一個併發包,其性能優於LinkedBlockingQueue和LinkedTransferQueue。 它儘可能使用ThreadLocal存儲來避免鎖定,但如果從ThreadLocal列表中沒有可用的BagEntry,那就從 公共的集合裏面獲取。 ThreadLocal 裏面的 狀態爲 Not-in-use 的 連接 也會出現 被“竊取”的情況, 它是一種“無鎖”的實現,使用專門的AbstractQueuedLongSynchronizer來管理跨線程信令。
請注意,從袋子中“借來”的BagEntry實際上並未從任何集合中刪除,因此即使放棄引用也不會進行垃圾收集。 因此必須通過方法“requite”來返還借用的對象,否則將導致內存泄漏。 只有“刪除”方法才能從袋子中完全刪除一個對象。
二、ConcurrentBag 分析
2.1 ConcurrentBag屬性
//用於線程本地化資源訪問
private final ThreadLocal<List<Object>> threadList;
// 存儲所有的公用資源
private final CopyOnWriteArrayList<T> sharedList;
// 是否是弱引用
private final boolean weakThreadLocals;
// 用於資源等待線程時的第一手資源交接
private final SynchronousQueue<T> handoffQueue;
下面是 對應的 構造函數:
public ConcurrentBag(final IBagStateListener listener)
{
this.listener = listener;
this.weakThreadLocals = useWeakThreadLocals();
this.handoffQueue = new SynchronousQueue<>(true);
this.waiters = new AtomicInteger();
this.sharedList = new CopyOnWriteArrayList<>();
if (weakThreadLocals) {
this.threadList = ThreadLocal.withInitial(() -> new ArrayList<>(16));
}
else {
this.threadList = ThreadLocal.withInitial(() -> new FastList<>(IConcurrentBagEntry.class, 16));
}
}
threadList :只能在同一個線程裏面纔可以獲取到自己對應的數據,裏面的list 是 FastList ,這個 也是對 List進行了一點優化, 詳細見下一節
sharedList :是CopyOnWriteArrayList 類型,這樣 讀數據的時候沒有鎖,寫的時候 加鎖了,但是是整體 替換 ,能達到最終一致性.
handoffQueue : 是 SynchronousQueue 類型,SynchronousQueue 是一個無緩存隊列
2.2 ConcurrentBag方法
2.2.1 borrow 方法
流程如下:
- 先從threadList 獲取狀態爲STATE_NOT_IN_USE 的 bagEntry ,從後向前獲取
- 如果沒有獲取到,從sharedList 裏面獲取,首先waiters++
- 如果獲取到bagEntry ,判斷waiters 是否 >1 ,大於1 說明 還有其他線程也在訪問,提醒包裹進行資源添加
- 如果還是 從sharedList 沒有獲取到bagEntry,那麼就從 handoffQueue 裏面 進行獲取,只要有
線程歸還bagEntry ,就可以 在第一時間獲取到 - 獲取到之後,判斷狀態是否爲STATE_NOT_IN_USE ,如果不是,還可以在timeout 時間內繼續poll
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
{
// 先從 threadList裏面獲取
final List<Object> list = threadList.get();
for (int i = list.size() - 1; i >= 0; i--) {
final Object entry = list.remove(i);
@SuppressWarnings("unchecked")
final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry;
}
}
//threadList 裏面沒有獲取到時, 從sharedList 裏面獲取
final int waiting = waiters.incrementAndGet();
try {
for (T bagEntry : sharedList) {
if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
// 同時有多個人在訪問,可能“搶走”了其他線程的資源,因此提醒包裹進行資源添加
if (waiting > 1) {
listener.addBagItem(waiting - 1);
}
return bagEntry;
}
}
listener.addBagItem(waiting);
timeout = timeUnit.toNanos(timeout);
// 在規定的時間內,可以多次poll 獲取狀態爲STATE_NOT_IN_USE的
do {
final long start = currentTime();
//從handoffQueue 獲取,等待時間爲timeout,獲取不到返回null
final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry;
}
timeout -= elapsedNanos(start);
} while (timeout > 10_000);
return null;
}
finally {
waiters.decrementAndGet();
}
}
2.2.2 requite方法
requite 方法會將借來的對象返回到包中。 借來但從未“退回”的物品將導致內存泄漏。
流程如下:
1.先將 bagEntry的狀態置爲STATE_NOT_IN_USE
2. 判斷是否 有waiters 請求線程,如果有,直接轉手資源
這裏會出現 迭代很久 bagEntry 的狀態還是 STATE_NOT_IN_USE,並且隊列裏面也沒有取的需求,這種情況可能時 此bagEntry 在shareList 裏面靠後,還沒有被迭代到,那就先掛起10毫秒
3.否則進行資源本地化
public void requite(final T bagEntry)
{
//先將狀態置爲 STATE_NOT_IN_USE
bagEntry.setState(STATE_NOT_IN_USE);
// 判斷是否存在等待線程,若存在,則直接轉手資源
for (int i = 0; waiters.get() > 0; i++) {
if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
}
// 如果迭代255次,那就說說明雖然很多的線程訪問,但是都是在sharedList 獲取階段
// 那就掛起10毫秒
else if ((i & 0xff) == 0xff) {
parkNanos(MICROSECONDS.toNanos(10));
}
else {
yield();
}
}
// 否則,進行資源本地化
final List<Object> threadLocalList = threadList.get();
if (threadLocalList.size() < 50) {
threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry);
}
}
ConcurrentBag中全部的資源均只能通過add方法進行添加,只能通過remove方法進行移出。
public void add(final T bagEntry)
{
if (closed) {
LOGGER.info("ConcurrentBag has been closed, ignoring add()");
throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()");
}
sharedList.add(bagEntry);
//旋轉直到有線程取走它或沒有任何線程等待
while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) {
yield();
}
}
三、FastList
fastList 也是實現了 List 接口,只是重寫實現了 get() 和 remove() 方法 ,將裏面的校驗rangeCheck(index)去掉了, 能稍微提升一點點點點的 速度吧,就是一行代碼
FastList 的get(int index) 方法:
@Override
public T get(int index)
{
return elementData[index];
}
ArrayList 的get(int index) 方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
同理,remove 也是類似
四、總結
這裏的borrow 做了一定的優化,用了 CAS + ThreadLocal 儘量避免鎖,提升了性能.