JDK8新增的StampedLock锁原理

StampedLock是并发包里面jdk8版本新增的一个锁,该锁提供了三种模式的读写控制,三种模式分别以下:数据库

  • 写锁writeLock,是个排它锁或者叫独占锁,同时只有一个线程能够获取该锁,当一个线程获取该锁后,其它请求的线程必须等待,当目前没有线程持有读锁或者写锁的时候才能够获取到该锁,请求该锁成功后会返回一个stamp 变量用来表示该锁的版本,当释放该锁时候须要unlockWrite并传递参数stamp。并发

  • 悲观读锁readLock,是个共享锁,在没有线程获取独占写锁的状况下,同时多个线程能够获取该锁,若是已经有线程持有写锁,其余线程请求获取该独占会被阻塞。这里讲的悲观实际上是参考数据库中的乐观悲观锁的,这里说的悲观是说在具体操做数据前悲观的认为其余线程可能要对本身操做的数据进行修改,因此须要先对数据加锁,这是在读少写多的状况下的一种考虑,请求该锁成功后会返回一个stamp变量用来表示该锁的版本,当释放该锁时候须要unlockRead并传递参数stamp。函数

  • 乐观读锁tryOptimisticRead,是相对于悲观锁来讲的,在操做数据前并无经过CAS设置锁的状态,若是当前没有线程持有写锁,则简单的返回一个非0的stamp版本信息,获取该stamp后在具体操做数据前还须要调用validate验证下该stamp是否已经不可用,也就是看当调用tryOptimisticRead返回stamp后到到当前时间间是否有其余线程持有了写锁,若是是那么validate会返回0,否者就可使用该stamp版本的锁对数据进行操做。因为tryOptimisticRead并无使用CAS设置锁状态因此不须要显示的释放该锁。该锁的一个特色是适用于读多写少的场景,由于获取读锁只是使用与或操做进行检验,不涉及CAS操做,因此效率会高不少,可是同时因为没有使用真正的锁,在保证数据一致性上须要拷贝一份要操做的变量到方法栈,而且在操做数据时候可能其余写线程已经修改了数据,而咱们操做的是方法栈里面的数据,也就是一个快照,因此最多返回的不是最新的数据,可是一致性仍是获得保障的。线程

下面经过JDK8注释里面的一个例子讲解来加深对上面讲解的理解。设计

输入图片说明

输入图片说明

输入图片说明

如上代码Point类里面有两个成员变量,和三个操做成员变量的方法,另外实例化了一个StampedLock对象用来保证操做的原子性。对象

首先分析下move方法,该函数做用是在添加增量,改变当前point坐标的位置,代码先获取到了写锁,而后对point坐标进行修改,而后释放锁。该锁是排它锁,这保证了其余线程调用move函数时候会被阻塞,直到当前线程显示释放了该锁,也就是保证了对变量x,y操做的原子性。blog

而后看下distanceFromOrigin方法,该方法做用是计算当前位置到原点的距离,代码(1)首先尝试获取乐观读锁,若是当前没有其它线程获取到了写锁,那么(1)会返回一个非0的stamp用来表示版本信息,代码(2)拷贝变量到本地方法栈里面,代码(3)检查在(1)获取到的是否还有效,之因此还要在此校验是由于代码(1)获取读锁时候并无经过CAS操做修改锁的状态而是简单的经过与或操做返回了一个版本信息,这里校验是看在在获取版本信息到如今的时间段里面是否有其余线程持有了写锁,若是有则以前获取的版本信息就无效了。这里若是校验成功则执行(7)使用本地方法栈里面的值进行计算而后返回。须要注意的是在代码(3)校验成功后,代码(7)计算中其余线程可能获取到了写锁而且修改了x,y的值,而当前线程执行代码(7)进行计算时候采用的才是对修改前值的拷贝,也就是操做的值是对以前值的一个拷贝,并非新的值。另外还有个问题,代码(2)和(3)可否互换,答案是不能,假设位置换了,那么首先执行validate,假如验证经过了,要拷贝x,y值到本地方法栈,而在拷贝的过程当中颇有可能其余线程已经修改了x,y中的一个,这就形成了数据的不一致性了。那么你可能会问,那不交换(2)和(3)时候在拷贝x,y值到本地方法栈里面时候也会存在其余线程修改了x,y中的一个值那,这个确实会存在,可是,别忘了拷贝后还有一道validate,若是这时候有线程修改了x,y中的值,那么确定是有线程在调用validate前sl.tryOptimisticRead后获取了写锁,那么validate时候就会失败。如今应该明白了吧,这也是乐观读设计的精妙之处也是使用时候容易出问题的地方。下面继续分析validate失败后会执行代码(4)获取悲观读锁,若是这时候骑行线程持有写锁则代码(4)会致使的当前线程阻塞直到其它线程释放了写锁。获取到读锁后,代码(5)拷贝变量到本地方法栈,而后就是代码(6)释放了锁,拷贝的时候因为加了读锁在拷贝期间其它线程获取写锁时候会被阻塞,这保证了数据的一致性。最后代码(7)使用方法栈里面数据计算返回,同理这里在计算时候使用的数据也可能不是最新的,其它写线程可能已经修改过原来的x,y值了。图片

最后一个方法moveIfAtOrigin方法做用是若是当前坐标为原点则移动到指定的位置。代码(1)获取悲观读锁,保证其它线程不能获取写锁修改x,y值,而后代码(2)判断若是当前点在原点则更新坐标,代码(3)尝试升级读锁为写锁,这里升级不必定成功,由于多个线程均可以同时获取悲观读锁,当多个线程都执行到(3)时候只有一个能够升级成功,升级成功则返回非0的stamp,否非返回0,这里假设当前线程升级成功,而后执行步骤(4)更新stamp值和坐标值而后退出循环,若是升级失败则执行步骤(5)首先释放读锁而后申请写锁,获取到写锁后在循环从新设置坐标值。最后步骤(6)释放锁。it

使用乐观读锁仍是很容易犯错误的,必需要当心,必需要保证以下的使用顺序:效率

输入图片说明

相关文章
相关标签/搜索