当前位置: 首页 > 科技观察

Java 8新特性探究(十):StampedLock将是解决同步问题的新宠

时间:2023-03-12 03:43:06 科技观察

Java8新特性探索(十):StampedLock将成为解决同步问题的新宠一个老话题,相信没有人喜欢同步代码,同步代码会降低应用的吞吐量等性能指标,最坏的情况下会hang和crash,但即使那样你也别无选择,因为必须保证信息的正确性。所以本文决定从synchronized和Lock到Java8新的StampedLock进行对比分析。我相信StampedLock不会让您失望。Synchronized在java5之前,同步主要是使用synchronized实现的。它是Java语言中的关键字。当用于修改方法或代码块时,可以保证最多有一个线程同时执行代码。同步块有四种不同类型:实例方法、静态方法、实例方法、静态方法中的同步块,这个大家应该很熟悉,就不说了,下面是代码示例synchronized(this)//dooperation}总结:synchronized一直是多线程并发编程中的老牌角色。很多人会称之为重量级锁。不过在JavaSE1.6中对Synchronized进行了各种优化后,性能也有所提升。Lock是Java5中java.util.concurrent.locks中新增的API。Lock是一个接口,核心方法有lock()、unlock()、tryLock(),实现类有ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.写锁;ReentrantReadWriteLock、ReentrantLock和同步锁都具有相同的内存语义。与synchronized不同的是,Lock是完全用Java编写的,与java层面的JVM实现无关。Lock提供了更灵活的锁定机制。很多synchronized没有提供的特性,比如锁投票,定时锁等待,中断锁等待,但是因为lock是代码实现的,要保证锁会被释放,必须把unLock()设置成finally{}下面是Lockrwlock.writeLock().lock();的代码示例try{//dooperation}finally{rwlock.writeLock().unlock();}总结:比synchronized更灵活,更具扩展性的锁机制,但无论如何,写一些synchronized代码更容易。StampedLock是java8中java.util.concurrent.locks中的一个新API。ReentrantReadWriteLock只有在没有读写锁的情况下才能获得写锁,可以用来实现悲观读(PessimisticReading),即如果在执行过程中进行读操作,可能经常会有另外一次执行需要写操作,为了保持同步,ReentrantReadWriteLock的读锁就可以派上用场了。但是,如果读执行多而写少,使用ReentrantReadWriteLock可能会导致写线程饿死(Starvation),即写线程无法竞争锁,一直处于等待状态。StampedLock控制锁有三种模式(写、读、乐观读),一个StampedLock状态由版本和模式组成,锁获取方法返回一个数字作为票戳,由相应的锁状态表示并控制访问,数字0表示没有为访问授予写锁。读锁分为悲观锁和乐观锁。所谓乐观读模式,即如果读操作多而写操作少,可以乐观地认为同时写读的机会很小,所以不悲观地使用fullread加锁,程序可以在读取数据后通过写入和执行来检查是否被更改,然后采取后续措施(重新读取更改信息,或者抛出异常)。这个小小的改进可以大大提高程序的吞吐量!!下面是javadocclassPoint{privatedoublex,y;提供的StampedLock示例privatefinalStampedLocksl=newStampedLock();voidmove(doubledeltaX,doubledeltaY){//独占锁定方法longstamp=sl.writeLock();最后{sl.unlockWrite(邮票);}}//看乐观读锁案例doubledistanceFromOrigin(){//Aread-onlymethodlongstamp=sl.tryOptimisticRead();//获取乐观读锁doublecurrentX=x,currentY=y;//读入两个字段local局部变量if(!sl.validate(stamp)){//查看乐观读锁发出后是否有其他写锁同时发生?stamp=sl.readLock();//如果没有,我们再次获取读悲观锁{sl.unlockRead(邮票);}}returnMath.sqrt(currentX*currentX+currentY*currentY);}//下面是一个悲观的读锁案例try{while(x==0.0&&y==0.0){//循环ring,检查当前状态是否一致longws=sl.tryConvertToWriteLock(stamp);//将读锁转换为写锁if(ws!=0L){//这是确认是否转换为写锁issuccessfulstamp=ws;//换票成功x=newX;//状态改变y=newY;//状态改变break;}else{//如果不能成功转换为写锁sl.unlockRead(stamp);//我们显式释放读锁stamp=sl.writeLock();//直接显式写锁然后重试通过循环}}}finally{sl.unlock(stamp);//释放读锁或写锁}}}总结:StampedLock需要比ReentrantReadWriteLock便宜,即消耗比较小。StampedLock和ReadWriteLock的性能比较与ReadWritLock比较。在一个线程的情况下,读取速度大约是4倍,写入速度是1倍。下图是在6线程的情况下,读性能提升几十倍,写性能提升近10倍:下图是吞吐量的提升:总结1.synchronized是在JVM层面实现的,不仅通过一些监控工具监控synchronized锁,当代码执行过程中出现异常时,JVM会自动释放锁;2、ReentrantLock、ReentrantReadWriteLock、StampedLock都是对象级锁。为保证锁会被释放,必须把unLock()放在finally{};3、StampedLock在吞吐量上有巨大的提升,尤其是在读线程越来越多的场景下;4.StampedLockAPI复杂,容易误用其他方法进行加锁操作;5、当只有少数竞争者时,synchronized是一个很好的通用锁实现;6.当线程增长可以预估时,ReentrantLock是一个很好的通用锁实现;StampedLock可以说是一个很好的锁实现,另外在吞吐量和性能上的提升也足以打动很多人,但并不代表它会取代之前的Lock。毕竟它还是有一些应用场景的。至少API比StampedLock更容易上手。下一篇博文会尽量更新快一些,说不定就是Nashorn的内容,让我试试看。..原文链接:http://my.oschina.net/benhaile/blog/264383