Java8新特性之StampedLock_动力节点Java学院整理
java8就像一个宝藏,一个小的api改进,也足与写一篇文章,比如同步,一直是多线程并发编程的一个老话题,相信没有人喜欢同步的代码,这会降低应用的吞吐量等性能指标,最坏的时候会挂起死机,但是即使这样你也没得选择,因为要保证信息的正确性。所以本文决定将从synchronized、lock到java8新增的stampedlock进行对比分析,相信stampedlock不会让大家失望。
synchronized
在java5之前,实现同步主要是使用synchronized。它是java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
有四种不同的同步块:
1.实例方法
2.静态方法
3.实例方法中的同步块
4.静态方法中的同步块
大家对此应该不陌生,所以不多讲了,以下是代码示例
synchronized(this) // do operation }
小结:在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着java se1.6对synchronized进行了各种优化之后,性能上也有所提升。
lock
它是java 5在java.util.concurrent.locks新增的一个api。
lock是一个接口,核心方法是lock(),unlock(),trylock(),实现类有reentrantlock, reentrantreadwritelock.readlock, reentrantreadwritelock.writelock;
reentrantreadwritelock, reentrantlock 和synchronized锁都有相同的内存语义。
与synchronized不同的是,lock完全用java写成,在java这个层面是无关jvm实现的。lock提供更灵活的锁机制,很多synchronized 没有提供的许多特性,比如锁投票,定时锁等候和中断锁等候,但因为lock是通过代码实现的,要保证锁定一定会被释放,就必须将unlock()放到finally{}中
下面是lock的一个代码示例
rwlock.writelock().lock(); try { // do operation } finally { rwlock.writelock().unlock(); }
小结:比synchronized更灵活、更具可伸缩性的锁定机制,但不管怎么说还是synchronized代码要更容易书写些
stampedlock
它是java8在java.util.concurrent.locks新增的一个api。
reentrantreadwritelock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(pessimistic reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,reentrantreadwritelock 的读取锁定就可派上用场。
然而,如果读取执行情况很多,写入很少的情况下,使用 reentrantreadwritelock 可能会使写入线程遭遇饥饿(starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。
stampedlock控制锁有三种模式(写,读,乐观读),一个stampedlock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。
所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!
下面是java doc提供的stampedlock一个例子
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); } //下面是悲观读锁案例 void moveifatorigin(double newx, double newy) { // upgrade // could instead start with optimistic, not read mode long stamp = sl.readlock(); try { while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合 long ws = sl.tryconverttowritelock(stamp); //将读锁转为写锁 if (ws != 0l) { //这是确认转为写锁是否成功 stamp = ws; //如果成功 替换票据 x = newx; //进行状态改变 y = newy; //进行状态改变 break; } else { //如果不能成功转换为写锁 sl.unlockread(stamp); //我们显式释放读锁 stamp = sl.writelock(); //显式直接进行写锁 然后再通过循环再试 } } } finally { sl.unlock(stamp); //释放读锁或写锁 } } }
小结:
stampedlock要比reentrantreadwritelock更加廉价,也就是消耗比较小。
stampedlock与readwritelock性能对比
下图是和readwritlock相比,在一个线程情况下,是读速度其4倍左右,写是1倍。
下图是六个线程情况下,读性能是其几十倍,写性能也是近10倍左右:
下图是吞吐量提高:
总结
1、synchronized是在jvm层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,jvm会自动释放锁定;
2、reentrantlock、reentrantreadwritelock,、stampedlock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unlock()放到finally{}中;
3、stampedlock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;
4、stampedlock有一个复杂的api,对于加锁操作,很容易误用其他方法;
5、当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;
6、当线程增长能够预估,reentrantlock是一个很好的通用的锁实现;
推荐阅读
-
Java8新特性之StampedLock_动力节点Java学院整理
-
Java8新特性之JavaFX 8_动力节点Java学院整理
-
Java8新特性之Base64详解_动力节点Java学院整理
-
Java8新特性之lambda的作用_动力节点Java学院整理
-
Java新特性之Nashorn_动力节点Java学院整理
-
Java8新特性之再见Permgen_动力节点Java学院整理
-
Java8新特性之类型注解_动力节点Java学院整理
-
Java8新特性之深入解析日期和时间_动力节点Java学院整理
-
Java8新特性之精简的JRE详解_动力节点Java学院整理
-
Java concurrency集合之ArrayBlockingQueue_动力节点Java学院整理