这几天在看公司的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.当条件满足后,操作后的状态如下: