深入理解StampedLock及其实现原理
StampedLock是java8在java.util.concurrent.locks新增的一个API。
ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取。然而,如果读取很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿问题,也就是写入线程无法竞争到锁定而一直处于等待状态。
StampedLock(印戳锁)是对ReentrantReadWriteLock读写锁的一种改进,主要的改进为:在没有写只有读的场景下,StampedLock支持不用加读锁而是直接进行读操作,最大程度提升读的效率,只有在发生过写操作之后,再加读锁才能进行读操作
StampedLock有三种模式的锁,用于控制读取/写入访问。StampedLock的状态由版本和模式组成。锁获取操作返回一个用于展示和访问锁状态的票据(stamp)变量,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。锁释放以及其他相关方法需要使用邮编(stamps)变量作为参数,如果他们和当前锁状态不符则失败,这三种模式为:
- 写入:方法writeLock可能为了获取独占访问而阻塞当前线程,返回一个stamp变量,能够在unlockWrite方法中使用从而释放锁。也提供了tryWriteLock。当锁被写模式所占有,没有读或者乐观的读操作能够成功。
- 读取:方法readLock可能为了获取非独占访问而阻塞当前线程,返回一个stamp变量,能够在unlockRead方法中用于释放锁。也提供了tryReadLock。
- 乐观读取:方法tryOptimisticRead返回一个非0邮编变量,仅在当前锁没有以写入模式被持有。如果在获得stamp变量之后没有被写模式持有,方法validate将返回true。这种模式可以被看做一种弱版本的读锁,可以被一个写入者在任何时间打断。乐观读取模式仅用于短时间读取操作时经常能够降低竞争和提高吞吐量。
StampedLock 的读写锁都是不可重入锁,所以在获取锁后释放锁前不应该再调用会获取锁的操作,以避免造成调用线程被阻塞
下面是java doc提供的StampedLock一个例子:
1 | public class Point { |
StampedLock源码分析
StampedLock也是通过一个int变量state、一个队列来实现的
state
state的默认值是256 1 0000 0000
state是一个int变量,总共有32位
前24位表示版本号、低8位表示锁。
低8位的第1位表示是否为写锁,1表示写锁、0表示没有写锁
剩下7位表示悲观读锁的个数
StampedLock引入乐观读,读时不加读锁,写锁也可进行写,避免写线程被饿死,读数据时读出来发现数据被修改了,再升级为悲观读,再读一次
StampedLock是一个读写锁,因此也会像读写锁那样,把一个state变量分成两半,分别表示读锁和写锁的状态,同时,还需要一个数据的version,但是,一次CAS没有办法操作两个变量,所以这个state变量本身同时也表示了数据的version。下面先分析state变量
1 | public class StampedLock implements java.io.Serializable { |
用最低的8位表示读和写的状态,其中第8位表示写锁的状态,最低的7位表示读锁的状态。因为写锁只有一个bit位,所以写锁是不可重入的
初始值不为0,而是把WBIT 向左移动了一位,也就是上面的ORIGIN 常量,构造方法如下所示
为什么state的初始值不设为0呢?看乐观锁的实现:
上面两个方法必须结合起来看:当state&WBIT != 0的时候,说明有线程持有写锁,上面的tryOptimisticRead会永远返回0。这样,再调用validate(stamp),也就是validate(0)也会永远返回false。这正是我们想要的逻辑:当有线程持有写锁的时候,validate永远返回false,无论写线程是否释放了写锁。因为无论是否释放了(state回到初始值)写锁,state值都不为0,所以validate(0)永远为false
为什么上面的validate(…)方法不直接比较stamp=state,而要比较state&SBITS=state&SBITS 呢?
因为读锁和读锁是不互斥的!
所以,即使在“乐观读”的时候,state 值被修改了,但如果它改的是第7位,validate(…)还是会返回true。
另外要说明的一点是,上面使用了内存屏障VarHandle.acquireFence();,是因为在这行代码的下一行里面的stamp、SBITS变量不是volatile的,由此可以禁止其和前面的currentX=X,currentY=Y进行重排序
通过上面的分析,可以发现state的设计非常巧妙。只通过一个变量,既实现了读锁、写锁的状态记录,还实现了数据的版本号的记录
转载自:
https://www.cnblogs.com/zmhjay1999/p/15183390.html
http://www.importnew.com/14941.html