当前位置:   article > 正文

Java8 StampedLock(一) 源码解析_stampedlock底层源码

stampedlock底层源码

 目录

一、定义

二、使用

1、读写锁

2、锁重入

3、非公平锁 

4、乐观读锁

5、锁转换


StampedLock是Java8引入的,对ReentrantReadWriteLock的一个改良和扩展,其底层实现不再基于AbstractQueuedSynchronizer了,本篇博客就来详细探讨该类的定义和使用。

一、定义

    StampedLock没有直接实现ReadWriteLock,但是提供了方法可以返回ReadWriteLock接口的实现类,如下:

可以通过asReadWriteLock方法与之前使用ReentrantReadWriteLock无缝兼容,也可以通过asReadLock和asWriteLock方法单独使用读锁或者写锁。

    StampedLock定义了多个内部类,如下:

ReadLockView和WriteLockView都实现了Lock接口,前者用于实现读锁,asReadLock方法就返回该类的实例;后者用于实现写锁,asWriteLock就返回该类的实例;ReadWriteLockView实现了ReadWriteLock接口,asReadWriteLock接口就返回该类的实例。这三个类的实现基于StampedLock的public方法的。如下:

 注意ReadLockView和WriteLockView的newCondition方法的实现都是抛出UnsupportedOperationException异常,因为StampedLock提供Condition接口的实现类,不过正常使用Condition接口都是直接使用ReentrantLock。

上面的WNode和AbstractQueuedSynchronizer的内部类Node基本一样,其定义如下:

Node中使用nextWaiter属性来记录锁的模式,这里单列了一个字段mode;status属性对应于Node中的waitStatus属性,cowait属性用来构成一个等待获取读锁的WNode链表,通过遍历这个链表就可以获取在此节点之后请求读锁的所有线程,避免遍历同步链表时判断是否读锁节点;其他三个字段和Node中的含义一样。

 StampedLock中定义了多个常量,如下:

  1. /**CPU的个数,用来判断自旋次数*/
  2. private static final int NCPU = Runtime.getRuntime().availableProcessors();
  3. /** 加入等待链表前自旋的最大次数,如果是多核,则最多自旋64*/
  4. private static final int SPINS = (NCPU > 1) ? 1 << 6 : 0;
  5. /** 等待head节点释放锁时第一轮for循环自旋(自旋通过内层的for循环实现)的最大次数,如果是多核,则最多自旋1024
  6. 如果还是无法获取锁,则进入第二轮for循环,自旋的最大次数扩容一倍,直到达到
  7. MAX_HEAD_SPINS为止不再扩容 */
  8. private static final int HEAD_SPINS = (NCPU > 1) ? 1 << 10 : 0;
  9. /** 等待head节点释放锁时一轮for循环自旋的最大次数,则最多自旋65536*/
  10. private static final int MAX_HEAD_SPINS = (NCPU > 1) ? 1 << 16 : 0;
  11. /** 执行yeild的周期 */
  12. private static final int OVERFLOW_YIELD_RATE = 7; // must be power 2 - 1
  13. /** 用来记录读锁重入次数的位数,第7位之后的位用来记录累计获取写锁的次数 */
  14. private static final int LG_READERS = 7;
  15. //获取读锁成功后state加上RUNIT
  16. private static final long RUNIT = 1L;
  17. //获取写锁和释放写锁成功后需要将state加上WBIT,然后通过第8位的位是否为1判断是否占有写锁
  18. private static final long WBIT = 1L << LG_READERS;
  19. //RBITS表示重入读锁的理论最大次数了,就是71,即127
  20. private static final long RBITS = WBIT - 1L;
  21. //实际允许重入读锁的最大次数,即126
  22. private static final long RFULL = RBITS - 1L;
  23. //ABITS就是81,将state与之求且,如果结果为0则表示此时读锁和写锁都没有被占用
  24. //如果结果大于WBIT,即第8位为1,表示写锁被占有了
  25. //如果结果等于RFULL则说明读锁重入的次数达到最大值了,多余次数通过readerOverflow属性记录
  26. private static final long ABITS = RBITS | WBIT;
  27. //57位是1,后7位是0,将state与之求且可以获取获取写锁的累计次数
  28. private static final long SBITS = ~RBITS; // note overlap with ABITS
  29. //初始值
  30. private static final long ORIGIN = WBIT << 1;
  31. //线程被中断后返回的特殊值
  32. private static final long INTERRUPTED = 1L;
  33. //WNode节点的状态,初始是0
  34. private static final int WAITING = -1;
  35. private static final int CANCELLED = 1;
  36. //读锁和写锁对应的模式
  37. private static final int RMODE = 0;
  38. private static final int WMODE = 1;

StampedLock包含的实例属性如下:

  1. /** 同步链表头 */
  2. private transient volatile WNode whead;
  3. /** 同步链表尾 */
  4. private transient volatile WNode wtail;
  5. // 实现读写锁接口的实例
  6. transient ReadLockView readLockView;
  7. transient WriteLockView writeLockView;
  8. transient ReadWriteLockView readWriteLockView;
  9. /** 记录锁的状态 */
  10. private transient volatile long state;
  11. /** 读锁重入次数达到最大值后,记录超过最大值的次数*/
  12. private transient int readerOverflow;

StampedLock包含的静态属性都是通过static代码块初始化,都是些关键属性的偏移量,用来实现原子的修改属性值,如下:

二、使用

1、读写锁

      StampedLock本身是为了优化ReentrantReadWriteLock的性能,其用法完全跟后者保持一致,可以借用后者的测试用例,如下:

  1. @Test
  2. public void test() throws Exception {
  3. StampedLock lock=new StampedLock();
  4. Lock readLock=lock.asReadLock();
  5. CyclicBarrier cyclicBarrier=new CyclicBarrier(6);
  6. Runnable a=new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  10. try {
  11. readLock.lock();
  12. Thread.sleep(1000);
  13. System.out.println("do something end for read,time->"+System.currentTimeMillis());
  14. cyclicBarrier.await();
  15. }catch (Exception e){
  16. e.printStackTrace();
  17. }finally {
  18. readLock.unlock();
  19. }
  20. }
  21. };
  22. for(int i=0;i<5;i++){
  23. new Thread(a).start();
  24. }
  25. long start=System.currentTimeMillis();
  26. cyclicBarrier.await();
  27. System.out.println("read end,time->"+(System.currentTimeMillis()-start));
  28. Lock writeLock=lock.asWriteLock();
  29. Runnable b=new Runnable() {
  30. @Override
  31. public void run() {
  32. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  33. try {
  34. writeLock.lock();
  35. Thread.sleep(1000);
  36. System.out.println("do something end for write,time->"+System.currentTimeMillis());
  37. }catch (Exception e){
  38. e.printStackTrace();
  39. }finally {
  40. writeLock.unlock();
  41. try {
  42. cyclicBarrier.await();
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }
  46. }
  47. }
  48. };
  49. for(int i=0;i<5;i++){
  50. new Thread(b).start();
  51. }
  52. start=System.currentTimeMillis();
  53. cyclicBarrier.await();
  54. System.out.println("write end,time->"+(System.currentTimeMillis()-start));
  55. }
  56. @Test
  57. public void test2() throws Exception {
  58. StampedLock lock=new StampedLock();
  59. Lock readLock=lock.asReadLock();
  60. Lock writeLock=lock.asWriteLock();
  61. CountDownLatch writeCount=new CountDownLatch(5);
  62. Runnable a=new Runnable() {
  63. @Override
  64. public void run() {
  65. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  66. try {
  67. readLock.lock();
  68. Thread.sleep(1000);
  69. System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
  70. try{
  71. //写锁必须等待所有的读锁释放才能获取锁,此处是读锁获取完成,等待获取写锁
  72. //就形成了死锁
  73. writeLock.lock();
  74. Thread.sleep(1000);
  75. System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
  76. }finally {
  77. writeLock.unlock();
  78. }
  79. }catch (Exception e){
  80. e.printStackTrace();
  81. }finally {
  82. readLock.unlock();
  83. writeCount.countDown();
  84. }
  85. }
  86. };
  87. for(int i=0;i<5;i++){
  88. new Thread(a).start();
  89. }
  90. long start=System.currentTimeMillis();
  91. writeCount.await();
  92. System.out.println("read end,time->"+(System.currentTimeMillis()-start));
  93. }
  94. @Test
  95. public void test3() throws Exception {
  96. StampedLock lock=new StampedLock();
  97. Lock readLock=lock.asReadLock();
  98. Lock writeLock=lock.asWriteLock();
  99. CountDownLatch writeCount=new CountDownLatch(5);
  100. Runnable a=new Runnable() {
  101. @Override
  102. public void run() {
  103. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  104. try {
  105. writeLock.lock();
  106. Thread.sleep(1000);
  107. System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
  108. try{
  109. //StampedLock下同一线程已经获取了写锁,不能再获取读锁,必须等待写锁释放
  110. readLock.lock();
  111. Thread.sleep(1000);
  112. System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
  113. }finally {
  114. readLock.unlock();
  115. }
  116. }catch (Exception e){
  117. e.printStackTrace();
  118. }finally {
  119. writeLock.unlock();
  120. writeCount.countDown();
  121. }
  122. }
  123. };
  124. for(int i=0;i<5;i++){
  125. new Thread(a).start();
  126. }
  127. long start=System.currentTimeMillis();
  128. writeCount.await();
  129. System.out.println("write end,time->"+(System.currentTimeMillis()-start));
  130. }
  131. @Test
  132. public void test4() throws Exception {
  133. StampedLock lock=new StampedLock();
  134. Lock readLock=lock.asReadLock();
  135. Lock writeLock=lock.asWriteLock();
  136. CountDownLatch writeCount=new CountDownLatch(2);
  137. Thread a=new Thread(new Runnable() {
  138. @Override
  139. public void run() {
  140. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  141. try {
  142. writeLock.lock();
  143. Thread.sleep(2000);
  144. System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
  145. }catch (Exception e){
  146. e.printStackTrace();
  147. }finally {
  148. writeLock.unlock();
  149. writeCount.countDown();
  150. }
  151. }
  152. });
  153. //让线程a先运行起来,持有写锁并sleep
  154. a.start();
  155. Thread.sleep(100);
  156. Thread b=new Thread(new Runnable() {
  157. @Override
  158. public void run() {
  159. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  160. try {
  161. //必须得等待占有写锁的线程释放写锁,才能获取读锁
  162. readLock.lock();
  163. Thread.sleep(2000);
  164. System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
  165. }catch (Exception e){
  166. e.printStackTrace();
  167. }finally {
  168. readLock.unlock();
  169. writeCount.countDown();
  170. }
  171. }
  172. });
  173. b.start();
  174. long start=System.currentTimeMillis();
  175. writeCount.await();
  176. System.out.println("main end,time->"+(System.currentTimeMillis()-start));
  177. }
  178. @Test
  179. public void test5() throws Exception {
  180. StampedLock lock=new StampedLock();
  181. Lock readLock=lock.asReadLock();
  182. Lock writeLock=lock.asWriteLock();
  183. CountDownLatch writeCount=new CountDownLatch(2);
  184. Thread a=new Thread(new Runnable() {
  185. @Override
  186. public void run() {
  187. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  188. try {
  189. readLock.lock();
  190. Thread.sleep(2000);
  191. System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
  192. }catch (Exception e){
  193. e.printStackTrace();
  194. }finally {
  195. readLock.unlock();
  196. writeCount.countDown();
  197. }
  198. }
  199. });
  200. //让线程a先运行起来,持有读锁并sleep
  201. a.start();
  202. Thread.sleep(100);
  203. Thread b=new Thread(new Runnable() {
  204. @Override
  205. public void run() {
  206. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  207. try {
  208. //其他线程占有读锁,必须等待读锁释放才能获取写锁
  209. writeLock.lock();
  210. Thread.sleep(2000);
  211. System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
  212. }catch (Exception e){
  213. e.printStackTrace();
  214. }finally {
  215. writeLock.unlock();
  216. writeCount.countDown();
  217. }
  218. }
  219. });
  220. b.start();
  221. long start=System.currentTimeMillis();
  222. writeCount.await();
  223. System.out.println("main end,time->"+(System.currentTimeMillis()-start));
  224. }

通过上述测试用例可知,StampedLock同ReentrantReadWriteLock在实现锁的语义上略有不同,StampedLock下读锁和写锁是完全互斥的,无论是否同一个线程占有的,获取其中一个锁的前提是另一个锁必须释放掉,而ReentrantReadWriteLock是不完全互斥的,如果一个线程持有写锁可以再次获取读锁的,其他线程无法获取读锁。

2、锁重入

      StampedLock下写锁是不可重入的,读锁是可重入的,而ReentrantReadWriteLock的读写锁都支持重入,参考如下测试用例:

  1. @Test
  2. public void test6() throws Exception {
  3. StampedLock stampedLock=new StampedLock();
  4. Lock lock=stampedLock.asWriteLock();
  5. CountDownLatch writeCount=new CountDownLatch(1);
  6. Thread a=new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  10. try {
  11. lock.lock();
  12. Thread.sleep(2000);
  13. System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
  14. System.out.println("isWriteLocked->"+stampedLock.isWriteLocked());
  15. try{
  16. //写锁无法重入,此处形成死锁
  17. lock.lock();
  18. System.out.println(stampedLock.getReadLockCount());
  19. Thread.sleep(2000);
  20. }finally {
  21. lock.unlock();
  22. }
  23. }catch (Exception e){
  24. e.printStackTrace();
  25. }finally {
  26. lock.unlock();
  27. writeCount.countDown();
  28. }
  29. }
  30. });
  31. //让线程a先运行起来,持有读锁并sleep
  32. a.start();
  33. long start=System.currentTimeMillis();
  34. writeCount.await();
  35. System.out.println("main end,time->"+(System.currentTimeMillis()-start));
  36. }
  37. @Test
  38. public void test7() throws Exception {
  39. StampedLock stampedLock=new StampedLock();
  40. Lock lock=stampedLock.asReadLock();
  41. CountDownLatch writeCount=new CountDownLatch(1);
  42. Thread a=new Thread(new Runnable() {
  43. @Override
  44. public void run() {
  45. System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
  46. try {
  47. lock.lock();
  48. Thread.sleep(2000);
  49. System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
  50. try{
  51. lock.lock();
  52. //是否获取读锁
  53. System.out.println("isReadLocked->"+stampedLock.isReadLocked());
  54. //获取读锁重入次数
  55. System.out.println("getReadLockCount->"+stampedLock.getReadLockCount());
  56. Thread.sleep(2000);
  57. }finally {
  58. lock.unlock();
  59. }
  60. }catch (Exception e){
  61. e.printStackTrace();
  62. }finally {
  63. lock.unlock();
  64. writeCount.countDown();
  65. }
  66. }
  67. });
  68. //让线程a先运行起来,持有读锁并sleep
  69. a.start();
  70. long start=System.currentTimeMillis();
  71. writeCount.await();
  72. System.out.println("main end,time->"+(System.currentTimeMillis()-start));
  73. }

3、非公平锁 

     ReentrantReadWriteLock的读写锁都支持公平锁和非公平锁两种模式,默认是非公平锁,而StampedLock只支持非公平锁,参考如下测试用例:

  1. @Test
  2. public void test8() throws Exception {
  3. //StampedLock只支持非公平锁
  4. // ReadWriteLock readWriteLock=new StampedLock().asReadWriteLock();
  5. //默认的非公平锁
  6. // ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
  7. //公平锁模式
  8. ReadWriteLock readWriteLock=new ReentrantReadWriteLock(true);
  9. Lock lock=readWriteLock.writeLock();
  10. CountDownLatch countDownLatch=new CountDownLatch(6);
  11. CyclicBarrier cyclicBarrier=new CyclicBarrier(4);
  12. Thread a=new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. System.out.println(Thread.currentThread().getName()+" start,time->"+System.nanoTime());
  16. try {
  17. lock.lock();
  18. Thread.sleep(5000);
  19. System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.nanoTime());
  20. }catch (Exception e){
  21. e.printStackTrace();
  22. }finally {
  23. lock.unlock();
  24. try {
  25. cyclicBarrier.await();
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. });
  32. //让线程a先运行起来,持有读锁并sleep
  33. a.start();
  34. Thread.sleep(100);
  35. Runnable runnable=new Runnable() {
  36. @Override
  37. public void run() {
  38. System.out.println(Thread.currentThread().getName()+" start,time->"+System.nanoTime());
  39. try {
  40. lock.lock();
  41. System.out.println(Thread.currentThread().getName()+" get lock,time->"+System.nanoTime());
  42. } finally {
  43. lock.unlock();
  44. countDownLatch.countDown();
  45. }
  46. }
  47. };
  48. //这3个线程肯定都已经加入到同步链表中
  49. for(int i=0;i<3;i++){
  50. Thread thread=new Thread(runnable);
  51. thread.start();
  52. }
  53. Thread.sleep(100);
  54. Runnable runnable2=new Runnable() {
  55. @Override
  56. public void run() {
  57. System.out.println(Thread.currentThread().getName()+" start,time->"+System.nanoTime());
  58. try {
  59. cyclicBarrier.await();
  60. lock.lock();
  61. System.out.println(Thread.currentThread().getName()+" get lock,time->"+System.nanoTime());
  62. } catch (Exception e){
  63. e.printStackTrace();
  64. } finally {
  65. lock.unlock();
  66. countDownLatch.countDown();
  67. }
  68. }
  69. };
  70. //这3个线程会在cyclicBarrier上等待,线程a释放锁后,这三个线程就会与已经在链表中的3个线程竞争锁
  71. for(int i=0;i<3;i++){
  72. Thread thread=new Thread(runnable2);
  73. thread.start();
  74. }
  75. Thread.sleep(100);
  76. countDownLatch.await();
  77. System.out.println("main end");
  78. }

在ReentrantReadWriteLock 公平锁模式下运行的结果如下:

  1. Thread-0 start,time->2431564443282691
  2. //1,2,3三个线程会进入同步链表中
  3. Thread-1 start,time->2431564542902209
  4. Thread-2 start,time->2431564542943641
  5. Thread-3 start,time->2431564543006405
  6. //4,5,6三个线程在cyclicBarrier上等待
  7. Thread-4 start,time->2431564643196134
  8. Thread-5 start,time->2431564643249053
  9. Thread-6 start,time->2431564643370479
  10. //0号线程准备释放锁了,释放完成后会唤醒在cyclicBarrier上等待的3个线程去抢占锁
  11. //因为是公平锁,这3个线程会依次加入到同步链表中,插入到3的后面
  12. //与此同时最早加入到同步链表中的1号线程会被唤醒抢占锁
  13. Thread-0 do something end for write,time->2431569442882313
  14. //各线程完全按照加入同步链表的顺序获取锁
  15. Thread-1 get lock,time->2431569443096449
  16. Thread-2 get lock,time->2431569443195724
  17. Thread-3 get lock,time->2431569443290075
  18. Thread-4 get lock,time->2431569443413552
  19. Thread-5 get lock,time->2431569443552618
  20. Thread-6 get lock,time->2431569443628920
  21. main end

 在ReentrantReadWriteLock非公平锁下输出如下:

  1. Thread-0 start,time->2431720679333140
  2. Thread-1 start,time->2431720770827359
  3. Thread-2 start,time->2431720770922121
  4. Thread-3 start,time->2431720770826129
  5. Thread-4 start,time->2431720870806643
  6. Thread-5 start,time->2431720870997397
  7. Thread-6 start,time->2431720871033907
  8. //非公平锁下,0号线程释放锁后就是同步链表头的的1号线程和此时几乎同步被唤醒的4,5,6三个线程
  9. //竞争锁
  10. Thread-0 do something end for write,time->2431725678408086
  11. //已经加入到同步链表的1,2,3号线程获取锁的顺序跟加入链表的顺序一致
  12. //5号线程是才开始抢占锁的但是优先于2,3号线程获取锁,即非公平锁的体现了
  13. //4,5,6三个线程获取锁的顺序是底层进程调度决定的,每次运行的结果可能都不同
  14. Thread-1 get lock,time->2431725678689500
  15. Thread-5 get lock,time->2431725678812977
  16. Thread-2 get lock,time->2431725678936864
  17. Thread-3 get lock,time->2431725679055419
  18. Thread-4 get lock,time->2431725679219508
  19. Thread-6 get lock,time->2431725679442260
  20. main end

 在StampedLock非公平锁下输出稍有不同,已经加入到链表中的1,2,3号线程并不是按照加入到同步链表的顺序获取锁,其输出如下:

  1. Thread-0 start,time->2432129070650348
  2. Thread-1 start,time->2432129169911740
  3. Thread-2 start,time->2432129169943738
  4. Thread-3 start,time->2432129170014706
  5. Thread-4 start,time->2432129270062087
  6. Thread-6 start,time->2432129270395599
  7. Thread-5 start,time->2432129270522358
  8. Thread-0 do something end for write,time->2432134069706423
  9. //1,2,3三个线程获取锁的顺序是不固定的,跟加入到同步链表的顺序无关
  10. //4号线程优先于1,3号线程获取锁就是非公平锁的体现
  11. //4,5,6获取锁的顺序同样是由底层进程调度决定的
  12. Thread-2 get lock,time->2432134069941481
  13. Thread-4 get lock,time->2432134070055524
  14. Thread-1 get lock,time->2432134070219203
  15. Thread-3 get lock,time->2432134070329553
  16. Thread-6 get lock,time->2432134070453030
  17. Thread-5 get lock,time->2432134070651168
  18. main end

这是因为StampedLock在把当前线程加入到同步链表前都做了自旋等待,只有自旋等待的次数达到最大值了才会加入到同步链表中,因为底层系统调度的原因,1,2,3号线程自旋达到最大次数的先后顺序不一定是线程启动的顺序,即不会按照线程启动的顺序加入到同步链表中,获取锁时是按照实际加入到同步链表的顺序获取的。如果把这三个线程的启动时间间隔拉大,启动一个线程后就休眠200ms,则会按照线程启动的顺序加入到同步链表中,并按照同步链表中的顺序获取锁,改动如下:

再执行效果就跟 ReentrantReadWriteLock非公平锁下的输出一样了。

4、乐观读锁

     ReentrantReadWriteLock下写锁必须等待所有的读锁都释放了才能获取,在读线程比较多写线程比较少的情形下,这会导致写线程可能会长期等待,StampedLock为了解决这个问题引入了乐观读锁。乐观读锁实际并不是一个锁,也没有实际的加锁解锁逻辑,只是判断了下是否写锁被占有了,如果没有则读取某个全局共享变量到本地,如果被占有了,则获取正常的读锁,与之对应,称为悲观读锁。当读取某个全局变量到本地后或者在使用拷贝到本地的本地变量的过程中,可能这时写锁被占用了,数据被修改了,导致原来读取的数据就是有问题的,调用方需要评估这种可能的数据不一致问题对业务的影响,测试用例如下:

  1. @Test
  2. public void test9() throws Exception {
  3. ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
  4. Lock readLock=readWriteLock.readLock();
  5. Lock writeLock=readWriteLock.writeLock();
  6. CyclicBarrier cyclicBarrier=new CyclicBarrier(12);
  7. int num=10000000;
  8. Runnable read=new Runnable() {
  9. @Override
  10. public void run() {
  11. try {
  12. cyclicBarrier.await();
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. for(int i=0;i<num;i++) {
  17. try {
  18. readLock.lock();
  19. Holder.getA();
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. } finally {
  23. readLock.unlock();
  24. }
  25. }
  26. try {
  27. cyclicBarrier.await();
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. };
  33. for(int i=0;i<10;i++){
  34. Thread thread=new Thread(read);
  35. thread.start();
  36. }
  37. Runnable write=new Runnable() {
  38. @Override
  39. public void run() {
  40. try {
  41. cyclicBarrier.await();
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. }
  45. for(int i=0;i<num;i++) {
  46. try {
  47. writeLock.lock();
  48. Holder.add();
  49. } catch (Exception e) {
  50. e.printStackTrace();
  51. } finally {
  52. writeLock.unlock();
  53. }
  54. }
  55. try {
  56. cyclicBarrier.await();
  57. } catch (Exception e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. };
  62. Thread b=new Thread(write);
  63. b.start();
  64. cyclicBarrier.await();
  65. long start=System.currentTimeMillis();
  66. System.out.println("run start");
  67. cyclicBarrier.await();
  68. System.out.println("run end,time->"+(System.currentTimeMillis()-start));
  69. }
  70. @Test
  71. public void test10() throws Exception {
  72. StampedLock stampedLock=new StampedLock();
  73. CyclicBarrier cyclicBarrier=new CyclicBarrier(12);
  74. int num=10000000;
  75. Runnable read=new Runnable() {
  76. @Override
  77. public void run() {
  78. try {
  79. cyclicBarrier.await();
  80. } catch (Exception e) {
  81. e.printStackTrace();
  82. }
  83. for(int i=0;i<num;i++) {
  84. try {
  85. long stamp=stampedLock.tryOptimisticRead();
  86. //读取共享变量
  87. int a=Holder.getA();
  88. //validate方法返回false表示占有了写锁,共享变量可能变了
  89. if(!stampedLock.validate(stamp)){
  90. try {
  91. //获取正常的悲观读锁,会阻塞等待修改完成,写锁释放
  92. stamp=stampedLock.readLock();
  93. //重新读取共享变量
  94. a=Holder.getA();
  95. } finally {
  96. //释放读锁
  97. stampedLock.unlockRead(stamp);
  98. }
  99. }
  100. } catch (Exception e) {
  101. e.printStackTrace();
  102. }
  103. }
  104. try {
  105. cyclicBarrier.await();
  106. } catch (Exception e) {
  107. e.printStackTrace();
  108. }
  109. }
  110. };
  111. for(int i=0;i<10;i++){
  112. Thread thread=new Thread(read);
  113. thread.start();
  114. }
  115. Runnable write=new Runnable() {
  116. @Override
  117. public void run() {
  118. try {
  119. cyclicBarrier.await();
  120. } catch (Exception e) {
  121. e.printStackTrace();
  122. }
  123. for(int i=0;i<num;i++) {
  124. long stamp=0;
  125. try {
  126. stamp=stampedLock.writeLock();
  127. Holder.add();
  128. } catch (Exception e) {
  129. e.printStackTrace();
  130. } finally {
  131. stampedLock.unlockWrite(stamp);
  132. }
  133. }
  134. try {
  135. cyclicBarrier.await();
  136. } catch (Exception e) {
  137. e.printStackTrace();
  138. }
  139. }
  140. };
  141. Thread b=new Thread(write);
  142. b.start();
  143. cyclicBarrier.await();
  144. long start=System.currentTimeMillis();
  145. System.out.println("run start");
  146. cyclicBarrier.await();
  147. System.out.println("run end,time->"+(System.currentTimeMillis()-start));
  148. }

test9耗时19s左右,test10耗时不足400ms,由此可见在读多写少的场景下,乐观读锁可以显著提供系统的处理效率,提高系统吞吐量,大幅减少锁等待的时间。 

5、锁转换

     StampedLock在正常的读写锁之外增加了乐观读锁,并支持在这三种锁之间有限的转换,所谓的转换就是不需要经过解锁直接加锁,避免代码冗余,具体如下:

1)转换成写锁的前提是从stamp对应的一次加锁到现在获取写锁的次数未发生改变,即这期间一直是读锁或者乐观读锁或者写锁一直未释放,然后满足以下条件之一:

  1. 当前状态是写锁,stamp也是写锁
  2. 当前状态是读锁,且锁重入次数只有一次,stamp占有读锁或者写锁
  3. 当前状态是乐观读锁,stamp是无锁状态

 测试用例如下:

  1. @Test
  2. public void test11() throws Exception {
  3. StampedLock stampedLock=new StampedLock();
  4. long stamp=0;
  5. try {
  6. stamp=stampedLock.readLock();
  7. System.out.println("get readLock succ");
  8. //此时会直接释放读锁并获取写锁
  9. long result=stampedLock.tryConvertToWriteLock(stamp);
  10. if(result==0){
  11. System.out.println("tryConvertToWriteLock fail");
  12. stampedLock.unlockRead(stamp);
  13. stamp=stampedLock.writeLock();
  14. }else{
  15. stamp=result;
  16. }
  17. System.out.println("get writeLock succ");
  18. } finally {
  19. stampedLock.unlockWrite(stamp);
  20. }
  21. }
  22. @Test
  23. public void test12() throws Exception {
  24. StampedLock stampedLock=new StampedLock();
  25. long stamp=0;
  26. try {
  27. stamp=stampedLock.readLock();
  28. long stamp2=stampedLock.readLock();
  29. System.out.println("get readLock succ");
  30. //此时会直接释放读锁并获取写锁
  31. long stamp3=stampedLock.tryConvertToWriteLock(stamp2);
  32. if(stamp3==0){
  33. System.out.println("tryConvertToWriteLock fail");
  34. stampedLock.unlockRead(stamp2);
  35. stampedLock.unlockRead(stamp);
  36. stamp=stampedLock.writeLock();
  37. }
  38. System.out.println("get writeLock succ");
  39. } finally {
  40. stampedLock.unlockWrite(stamp);
  41. }
  42. }
  43. @Test
  44. public void test13() throws Exception {
  45. StampedLock stampedLock=new StampedLock();
  46. long stamp=0;
  47. try {
  48. stamp=stampedLock.tryOptimisticRead();
  49. System.out.println("get OptimisticRead succ");
  50. //此时会直接释放读锁并获取写锁
  51. long result=stampedLock.tryConvertToWriteLock(stamp);
  52. if(result==0){
  53. System.out.println("tryConvertToWriteLock fail");
  54. stampedLock.unlockRead(stamp);
  55. stamp=stampedLock.writeLock();
  56. }else{
  57. stamp=result;
  58. }
  59. System.out.println("get writeLock succ");
  60. } finally {
  61. stampedLock.unlockWrite(stamp);
  62. }
  63. }

2) 转换成读锁的前提跟转换成写锁的前提一样,从stamp对应的一次加锁到现在获取写锁的次数未发生改变,然后满足以下条件之一:

  1. 当前是写锁,stamp也是写锁
  2. 当前是乐观读锁,stamp也是乐观读锁
  3. 当前是读锁,stamp也是读锁

测试用例如下:

  1. @Test
  2. public void test15() throws Exception {
  3. StampedLock stampedLock=new StampedLock();
  4. long stamp=0;
  5. try {
  6. stamp=stampedLock.writeLock();
  7. System.out.println("get writeLock succ");
  8. //此时会直接释放写锁,获取读锁,然后唤醒下一个等待写锁的线程
  9. long result=stampedLock.tryConvertToReadLock(stamp);
  10. if(result==0){
  11. System.out.println("tryConvertToReadLock fail");
  12. stampedLock.unlockWrite(stamp);
  13. stamp=stampedLock.readLock();
  14. }else{
  15. stamp=result;
  16. }
  17. System.out.println("get readLock succ");
  18. } finally {
  19. stampedLock.unlockRead(stamp);
  20. }
  21. }
  22. @Test
  23. public void test16() throws Exception {
  24. StampedLock stampedLock=new StampedLock();
  25. long stamp=0;
  26. try {
  27. stamp=stampedLock.tryOptimisticRead();
  28. System.out.println("get writeLock succ");
  29. //此时会直接释放写锁,获取读锁,然后唤醒下一个等待写锁的线程
  30. long result=stampedLock.tryConvertToReadLock(stamp);
  31. if(result==0){
  32. System.out.println("tryConvertToReadLock fail");
  33. stamp=stampedLock.readLock();
  34. }else{
  35. stamp=result;
  36. }
  37. System.out.println("get readLock succ");
  38. } finally {
  39. stampedLock.unlockRead(stamp);
  40. }
  41. }

3)转换成乐观读锁的前提跟转换成写锁的前提一样,从stamp对应的一次加锁到现在获取写锁的次数未发生改变,然后满足以下条件之一:

  1. stamp和当前的锁都是写锁或者读锁,会释放锁
  2. stamp和当前的锁都是乐观读锁

测试用例如下:

  1. @Test
  2. public void test17() throws Exception {
  3. StampedLock stampedLock=new StampedLock();
  4. long stamp=0;
  5. try {
  6. stamp=stampedLock.writeLock();
  7. System.out.println("get writeLock succ");
  8. //此时会直接释放写锁,获取读锁,然后唤醒下一个等待写锁的线程
  9. long result=stampedLock.tryConvertToOptimisticRead(stamp);
  10. if(result==0){
  11. System.out.println("tryConvertToOptimisticRead fail");
  12. stampedLock.unlockWrite(stamp);
  13. stamp=stampedLock.tryOptimisticRead();
  14. }else{
  15. stamp=result;
  16. }
  17. System.out.println("get OptimisticRead succ");
  18. } finally {
  19. System.out.println(""+stampedLock.validate(stamp));
  20. }
  21. }
  22. @Test
  23. public void test18() throws Exception {
  24. StampedLock stampedLock=new StampedLock();
  25. long stamp=0;
  26. try {
  27. stamp=stampedLock.readLock();
  28. System.out.println("get readLock succ");
  29. //此时会直接释放写锁,获取读锁,然后唤醒下一个等待写锁的线程
  30. long result=stampedLock.tryConvertToOptimisticRead(stamp);
  31. if(result==0){
  32. System.out.println("tryConvertToOptimisticRead fail");
  33. stampedLock.unlockRead(stamp);
  34. stamp=stampedLock.tryOptimisticRead();
  35. }else{
  36. stamp=result;
  37. }
  38. System.out.println("get OptimisticRead succ");
  39. } finally {
  40. System.out.println(""+stampedLock.validate(stamp));
  41. }
  42. }

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号