和AtomicLong相似的使用方式,可是性能比AtomicLong更好。 java
LongAdder与AtomicLong都是使用了原子操做来提升性能。可是LongAdder在AtomicLong的基础上进行了热点分离,热点分离相似于有锁操做中的减少锁粒度,将一个锁分离成若干个锁来提升性能。在无锁中,也能够用相似的方式来增长CAS的成功率,从而提升性能。 编程
LongAdder原理图: 设计模式
AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均经过CAS 指令从机器指令级别操做保证并发的原子性。惟一会制约AtomicLong高效的缘由是高并发,高并发意味着CAS的失败概率更高, 重试次数更多,越多线程重试,CAS失败概率又越高,变成恶性循环,AtomicLong效率下降。 安全
而LongAdder将把一个value拆分红若干cell,把全部cell加起来,就是value。因此对LongAdder进行加减操做,只须要对不一样的cell来操做,不一样的线程对不一样的cell进行CAS操做,CAS的成功率固然高了(试想一下3+2+1=6,一个线程3+1,另外一个线程2+1,最后是8,LongAdder没有乘法除法的API)。 多线程
但是在并发数不是很高的状况,拆分红若干的cell,还须要维护cell和求和,效率不如AtomicLong的实现。LongAdder用了巧妙的办法来解决了这个问题。 并发
初始状况,LongAdder与AtomicLong是相同的,只有在CAS失败时,才会将value拆分红cell,每失败一次,都会增长cell的数量,这样在低并发时,一样高效,在高并发时,这种“自适应”的处理方式,达到必定cell数量后,CAS将不会失败,效率大大提升。 异步
LongAdder是一种以空间换时间的策略。
ide
实现CompletionStage接口(40余个方法),大多数方法多数应用在函数式编程中。而且支持流式调用 函数式编程
CompletableFuture是Java 8中对Future的加强版 函数
简单实现:
import java.util.concurrent.CompletableFuture; public class AskThread implements Runnable { CompletableFuture<Integer> re = null; public AskThread(CompletableFuture<Integer> re) { this.re = re; } @Override public void run() { int myRe = 0; try { myRe = re.get() * re.get(); } catch (Exception e) { } System.out.println(myRe); } public static void main(String[] args) throws InterruptedException { final CompletableFuture<Integer> future = new CompletableFuture<Integer>(); new Thread(new AskThread(future)).start(); // 模拟长时间的计算过程 Thread.sleep(1000); // 告知完成结果 future.complete(60); } }Future最使人诟病的就是要等待,要本身去检查任务是否完成了,在Future中,任务完成的时间是不可控的。而 CompletableFuture的最大改进在于,任务完成的时间也开放了出来。
future.complete(60);用来设置完成时间。
CompletableFuture的异步执行:
public static Integer calc(Integer para) { try { // 模拟一个长时间的执行 Thread.sleep(1000); } catch (InterruptedException e) { } return para * para; } public static void main(String[] args) throws InterruptedException, ExecutionException { final CompletableFuture<Integer> future = CompletableFuture .supplyAsync(() -> calc(50)); System.out.println(future.get()); }CompletableFuture的流式调用:
public static Integer calc(Integer para) { try { // 模拟一个长时间的执行 Thread.sleep(1000); } catch (InterruptedException e) { } return para * para; } public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu = CompletableFuture .supplyAsync(() -> calc(50)) .thenApply((i) -> Integer.toString(i)) .thenApply((str) -> "\"" + str + "\"") .thenAccept(System.out::println); fu.get(); }
组合多个CompletableFuture:
public static Integer calc(Integer para) { return para / 2; } public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu = CompletableFuture .supplyAsync(() -> calc(50)) .thenCompose( (i) -> CompletableFuture.supplyAsync(() -> calc(i))) .thenApply((str) -> "\"" + str + "\"") .thenAccept(System.out::println); fu.get(); }这几个例子更可能是侧重Java8的一些新特性,这里就简单举下例子来讲明特性,就不深究了。
CompletableFuture跟性能上关系不大,更多的是为了支持函数式编程,在功能上的加强。固然开放了完成时间的设置是一大亮点。
在上一篇中刚刚提到了锁分离,而锁分离的重要的实现就是ReadWriteLock。而StampedLock则是ReadWriteLock的一个改进。StampedLock与ReadWriteLock的区别在于,StampedLock认为读不该阻塞写,StampedLock认为当读写互斥的时候,读应该是重读,而不是不让写线程写。这样的设计解决了读多写少时,使用ReadWriteLock会产生写线程饥饿现象。
因此StampedLock是一种偏向于写线程的改进。
StampedLock示例:
import java.util.concurrent.locks.StampedLock; public class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; if (!sl.validate(stamp)) { stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }上述代码模拟了写线程和读线程, StampedLock根据stamp来查看是否互斥,写一次stamp变增长某个值
tryOptimisticRead()就是刚刚所说的读写不互斥的状况。
每次读线程要读时,会先判断
if (!sl.validate(stamp))validate中会先查看是否有写线程在写,而后再判断输入的值和当前的 stamp是否相同,即判断是否读线程将读到最新的数据。若是有写线程在写,或者 stamp数值不一样,则返回失败。
若是判断失败,固然能够重复的尝试去读,在示例代码中,并无让其重复尝试读,而采用的是将乐观锁退化成普通的读锁去读,这种状况就是一种悲观的读法。
stamp = sl.readLock();StampedLock的实现思想:
CLH自旋锁:当锁申请失败时,不会当即将读线程挂起,在锁当中会维护一个等待线程队列,全部申请锁,可是没有成功的线程都记录在这个队列中。每个节点(一个节点表明一个线程),保存一个标记位(locked),用于判断当前线程是否已经释放锁。当一个线程试图得到锁时,取得当前等待队列的尾部节点做为其前序节点。并使用相似以下代码判断前序节点是否已经成功释放锁
while (pred.locked) { }这个循环就是不断等前面那个结点释放锁,这样的自旋使得当前线程不会被操做系统挂起,从而提升了性能。
固然不会进行无休止的自旋,会在若干次自旋后挂起线程。
系列:
1. http://developer.51cto.com/art/201404/436505.htm