這幾天在看公司的BinLogRelover的時候, 看到大佬在WIKI裏面特意指出,爲何使用Guava提供的ListenableFuture來做一些回調功能, 而且還指出在高併發的場景下Java8提供的CompletableFuture
並不如人意,其實我個人還是比較喜歡CompletableFuture
的, 哈哈, 可能是以前寫代碼用的比較多吧。
ListenableFuture
用的不是很多, 所以優缺點也說不清楚, 但是CompletableFuture
經常讓人詬病的大概包含兩點吧,
- cancel(true)
大家使用CompletableFuture
肯定都是想使用他的異步編排能力,但是CompletableFuture在實現Future接口的同事,卻做了一些改變, 例如cancel()方法,一不小心就會踩坑。 在FutureTask中,cancel()會根據參數選擇是否中斷執行線程, 這一點在一些框架代碼中經常會用到,
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
整體的執行流程也很清晰,也正是因爲此,一些人在使用FutureTask的時候,都會保留任務執行異常的時候,調用一下cancel(true), 進而中斷框架中執行線程。
但如果你切換到 CompletableFuture
的時候, 可就要小心了,CompletableFuture
的此方法可並不會做相同的功能。因爲在CompletableFuture中一切都是用AltResult來保存的,包括異常,中斷等信息。
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = (result == null) &&
internalComplete(new AltResult(new CancellationException()));
postComplete();
return cancelled || isCancelled();
}
如果你之前在一些框架中特意處理中斷異常的話, 就要考慮下切換的時候如何保證手動觸發了。
- Completabk的無鎖棧機制究竟是否高效
這段代碼是Java併發實戰裏面提供的一段無鎖棧的實現代碼, 如果你非常瞭解Java的CAS機制的花,我像是很容易看懂的。
public class ConcurrentStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = top.get();
if (oldHead == null)
return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
public final E item;
public Node<E> next;
public Node(E item) {
this.item = item;
}
}
}
原理
該算法的基本原理是:只有當您知道要添加的項目是自開始操作以來唯一添加的項目時,纔會添加新的項目。 這是通過使用比較和交換完成的。 在添加新項目時使用堆棧,將堆棧的頂部放在新項目之後。 然後,將這個新構造的頭元素(舊頭)的第二個項目與當前項目進行比較。 如果兩者匹配,那麼你可以將舊頭換成新頭,否則就意味着另一個線程已經向堆棧添加了另一個項目,在這種情況下,你必須再試一次。
當從堆棧中彈出一個項目時,在返回項目之前,您必須檢查另一個線程自操作開始以來沒有添加其他項目。
例如Push操作
- 首先單鏈表保存了各個
Stack
中的各個元素,成員變量top持有了棧的棧頂元素。 - 當執行push操作時,首先創建一個新的元素爲
newHead
,並讓該新節點的next
指針指向top節點(此時top=oldHead
)。 - 最後通過CAS替換
top=newHead
,CAS的交換條件是top=oldHead。
當條件滿足後,操作後的狀態如下:
POP操作
- 當執行pop操作時,創建一個新的指針,該指針指向top的next元素。
- 然後通過
CAS
替換top=newHead
,CAS的交換條件是top=oldHead
。
3.當條件滿足後,操作後的狀態如下: