当前位置:   article > 正文

多线程JUC 第2季 读写锁ReentrantReadWriteLock&stamplock邮戳锁

多线程JUC 第2季 读写锁ReentrantReadWriteLock&stamplock邮戳锁

读写锁ReentrantReadWriteLock

1.1  ReentrantReadWriteLock的特点

reentrantReadWriteloc 解决主要解决的是 读读共享,读写互斥。在读多写少的场景下,读写锁具有较高的性能。

它只允许读读共存,而读写和写写依然是互斥的,大多实际场景是“读/读”线程间并不存在互斥关系。只有“读/写”线程或者“写/写”线程间的操作需要互斥的,因此引入ReentrantReadWriteLock。

一个ReentrantReadWriteLock同时只能存在一个写锁,但是可以存在多个读锁,但不能同时存在写锁和读锁(切菜还是拍菜选一个),也即一个锁可以被多个读操作访问  或者 一个写操作访问,但两者不能同时进行。

只有在读多写少的场景之下,读写锁才具有较高的性能体现。

1.2 对写锁降级过程

降级过程: 写锁变为读锁的过程。读锁不能升级为写锁。在ReentrantReadWriteLock中,当读锁在使用时,如果有线程尝试获取写锁,该写线程将会被阻塞,所以,需要释放读锁后,才可以获取写锁。

1.3 案例代码:读读共享,读写互斥

1.3.1 实例代码-资源类

  1. package com.ljf.thread.readwritelock;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.concurrent.locks.ReadWriteLock;
  5. import java.util.concurrent.locks.ReentrantLock;
  6. import java.util.concurrent.locks.ReentrantReadWriteLock;
  7. /**
  8. * @ClassName: MyResources
  9. * @Description: TODO
  10. * @Author: admin
  11. * @Date: 2023/05/21 11:33:08 
  12. * @Version: V1.0
  13. **/
  14. public class MyResources {
  15. ReentrantLock mylock=new ReentrantLock();
  16. Map<String,String> resultMap=new HashMap<>();
  17. ReadWriteLock myrwLock=new ReentrantReadWriteLock();
  18. public void writeSomething(String key,String value){
  19. myrwLock.writeLock().lock();
  20. try {
  21. System.out.println("线程:"+Thread.currentThread().getName()+"正在写...");
  22. Thread.sleep(500);
  23. resultMap.put(key,value);
  24. System.out.println("线程:"+Thread.currentThread().getName()+"写入完成...");
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. finally {
  29. myrwLock.writeLock().unlock();
  30. }
  31. }
  32. public void readSomething(String key){
  33. myrwLock.readLock().lock();
  34. try {
  35. System.out.println("线程:"+Thread.currentThread().getName()+"正在读...");
  36. resultMap.get(key);
  37. Thread.sleep(200);
  38. System.out.println("线程:"+Thread.currentThread().getName()+"读取完成...");
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. finally {
  43. myrwLock.readLock() .unlock();
  44. }
  45. }
  46. }

1.3.2 实例代码-调用类

  1. package com.ljf.thread.readwritelock;
  2. /**
  3. * Hello world!
  4. *
  5. */
  6. public class App
  7. {
  8. public static void main( String[] args )
  9. {
  10. System.out.println( "Hello World!" );
  11. MyResources myResources=new MyResources();
  12. for(int k=0;k<10;k++){
  13. final int m=k;
  14. new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. myResources.writeSomething("do"+m,m+"");
  18. }
  19. }).start();
  20. }
  21. System.out.println("=====");
  22. for(int k=0;k<10;k++){
  23. final int m=k;
  24. new Thread(new Runnable() {
  25. @Override
  26. public void run() {
  27. myResources.readSomething("do"+m);
  28. }
  29. }).start();
  30. }
  31. }
  32. }

1.3.3 结论分析

A:线程0-9 都是写线程,写线程互斥,线程一个写完,另外一个写;

B:线程10-19 线程都是读,读线程共享,多个线程可以同时读

说明: A内部之间,A和B之间都是  阻塞的,互斥的; B内部之间的读线程都是共享的

  1. Hello World!
  2. 线程:Thread-1正在写...
  3. =====
  4. 线程:Thread-1写入完成...
  5. 线程:Thread-3正在写...
  6. 线程:Thread-3写入完成...
  7. 线程:Thread-0正在写...
  8. 线程:Thread-0写入完成...
  9. 线程:Thread-2正在写...
  10. 线程:Thread-2写入完成...
  11. 线程:Thread-4正在写...
  12. 线程:Thread-4写入完成...
  13. 线程:Thread-5正在写...
  14. 线程:Thread-5写入完成...
  15. 线程:Thread-6正在写...
  16. 线程:Thread-6写入完成...
  17. 线程:Thread-7正在写...
  18. 线程:Thread-7写入完成...
  19. 线程:Thread-8正在写...
  20. 线程:Thread-8写入完成...
  21. 线程:Thread-9正在写...
  22. 线程:Thread-9写入完成...
  23. 线程:Thread-10正在读...
  24. 线程:Thread-11正在读...
  25. 线程:Thread-12正在读...
  26. 线程:Thread-13正在读...
  27. 线程:Thread-14正在读...
  28. 线程:Thread-15正在读...
  29. 线程:Thread-17正在读...
  30. 线程:Thread-18正在读...
  31. 线程:Thread-16正在读...
  32. 线程:Thread-19正在读...
  33. 线程:Thread-15读取完成...
  34. 线程:Thread-19读取完成...
  35. 线程:Thread-16读取完成...
  36. 线程:Thread-12读取完成...
  37. 线程:Thread-14读取完成...
  38. 线程:Thread-11读取完成...
  39. 线程:Thread-13读取完成...
  40. 线程:Thread-10读取完成...
  41. 线程:Thread-18读取完成...
  42. 线程:Thread-17读取完成...

1.4 案例代码:读写互斥,读读共享,读线程没有完成时,其它写线程无法获取锁。

1.4.1 实例代码-资源类

  1. package com.ljf.thread.readwritelock;
  2. /**
  3. * Hello world!
  4. *
  5. */
  6. public class App
  7. {
  8. public static void main( String[] args )
  9. {
  10. System.out.println( "Hello World!" );
  11. MyResources myResources=new MyResources();
  12. for(int k=0;k<10;k++){
  13. final int m=k;
  14. new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. myResources.writeSomething("do"+m,m+"");
  18. }
  19. }).start();
  20. }
  21. System.out.println("=====");
  22. for(int k=0;k<10;k++){
  23. final int m=k;
  24. new Thread(new Runnable() {
  25. @Override
  26. public void run() {
  27. myResources.readSomething("do"+m);
  28. }
  29. }).start();
  30. }
  31. for (int i = 1; i <=3; i++) {
  32. final int m=i;
  33. new Thread(new Runnable() {
  34. @Override
  35. public void run() {
  36. myResources.writeSomething("do"+m,m+"");
  37. }
  38. }).start();
  39. }
  40. }
  41. }

1.4.2 结论

结果:

A:线程0-9 都是写线程,写线程互斥,线程一个写完,另外一个写;

B:线程10-19 线程都是读,读线程共享,多个线程可以同时读

C: 线程20-22 都是写线程,写线程互斥,线程一个写完,另外一个写;

说明: A内部之间,A和B之间都是  阻塞的,互斥的; B内部之间的读线程都是共享的

A内部之间,A和B,B和C ,C内部之间,读写互斥,

B内部之间读读共享,B和C,B读线程没有完成时,其它写线程C无法获取锁。

线程:Thread-0正在写...
=====
线程:Thread-0写入完成...
线程:Thread-1正在写...
线程:Thread-1写入完成...
线程:Thread-2正在写...
线程:Thread-2写入完成...
线程:Thread-3正在写...
线程:Thread-3写入完成...
线程:Thread-4正在写...
线程:Thread-4写入完成...
线程:Thread-5正在写...
线程:Thread-5写入完成...
线程:Thread-6正在写...
线程:Thread-6写入完成...
线程:Thread-7正在写...
线程:Thread-7写入完成...
线程:Thread-8正在写...
线程:Thread-8写入完成...
线程:Thread-9正在写...
线程:Thread-9写入完成...
线程:Thread-10正在读...
线程:Thread-11正在读...
线程:Thread-12正在读...
线程:Thread-13正在读...
线程:Thread-14正在读...
线程:Thread-16正在读...
线程:Thread-15正在读...
线程:Thread-17正在读...
线程:Thread-19正在读...
线程:Thread-18正在读...
线程:Thread-11读取完成...
线程:Thread-10读取完成...
线程:Thread-12读取完成...
线程:Thread-16读取完成...
线程:Thread-14读取完成...
线程:Thread-18读取完成...
线程:Thread-19读取完成...
线程:Thread-13读取完成...
线程:Thread-17读取完成...
线程:Thread-15读取完成...
线程:Thread-20正在写...
线程:Thread-20写入完成...
线程:Thread-21正在写...
线程:Thread-21写入完成...
线程:Thread-22正在写...
线程:Thread-22写入完成...

二 邮戳锁

2.1 邮戳锁

2.2.1 邮戳锁的作用

ReentrantReadWriteLock读锁被占用的时候,其他线程尝试获取写锁的时候被阻塞。但是,stampedLock采取乐观锁获取锁后,其他线程尝试获取写锁时不会被阻塞,这其实是对读锁的优化,所以,在获取乐观读锁后,还需要对结果进行校验。

所有获取锁的方法,都返回一个邮戳,stamp为零表示获取失败,其余都表示成功。

所有释放锁的方法,都需要一个邮戳,这个stamp必须是和成功获取锁时得到的stamp一致。

Stampedlock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)

2.2.2 邮戳锁的模式

Stampedlock的3种访问模式:

 1.reading 读模式悲观,功能和ReentrantReadWriteLock的读锁类似。

 2.writing 写模式:功能和ReentrantReadWriteLock的写锁类似。

 3.optimistic reading 乐观锁模式: 无锁机制,类似数据库中的乐观锁,支持读写开发,很乐观认为读取时没人修改,加入被修改再实现升级为悲观读模式。

2.2 代码

1.代码:  

//暂停2秒钟线程
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

  1. package com.ljf.thread.readwritelock.stamp;
  2. import java.util.concurrent.TimeUnit;
  3. import java.util.concurrent.locks.StampedLock;
  4. /**
  5. * @ClassName: StampLock
  6. * @Description: TODO
  7. * @Author: admin
  8. * @Date: 2023/10/01 21:26:47 
  9. * @Version: V1.0
  10. **/
  11. public class StampLock {
  12. static int number = 37;
  13. static StampedLock stampedLock = new StampedLock();
  14. public static void main(String[] args) {
  15. StampLock resource=new StampLock();
  16. new Thread(() -> {
  17. resource.tryOptimisticRead();
  18. },"readThread").start();
  19. //暂停2秒钟线程,读过程可以写介入,演示
  20. //try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
  21. //暂停6秒钟线程
  22. try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
  23. new Thread(() -> {
  24. System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
  25. resource.write();
  26. },"writeThread").start();
  27. }
  28. //乐观读,读的过程中也允许获取写锁介入
  29. public void tryOptimisticRead()
  30. {
  31. long stamp = stampedLock.tryOptimisticRead();
  32. int result = number;
  33. //故意间隔4秒钟,很乐观认为读取中没有其它线程修改过number值,具体靠判断
  34. System.out.println("4秒前stampedLock.validate方法值(true无修改,false有修改)"+"\t"+stampedLock.validate(stamp));
  35. for (int i = 0; i < 4; i++) {
  36. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
  37. System.out.println(Thread.currentThread().getName()+"\t"+"正在读取... "+i+" 秒" +
  38. "后stampedLock.validate方法值(true无修改,false有修改)"+"\t"+stampedLock.validate(stamp));
  39. }
  40. if(!stampedLock.validate(stamp))
  41. {
  42. System.out.println("有人修改过------有写操作");
  43. stamp = stampedLock.readLock();
  44. try
  45. {
  46. System.out.println("从乐观读 升级为 悲观读");
  47. result = number;
  48. System.out.println("重新悲观读后result:"+result);
  49. }finally {
  50. stampedLock.unlockRead(stamp);
  51. }
  52. }
  53. System.out.println(Thread.currentThread().getName()+"\t"+" finally value: "+result);
  54. }
  55. public void write()
  56. {
  57. long stamp = stampedLock.writeLock();
  58. System.out.println(Thread.currentThread().getName()+"\t"+"写线程准备修改");
  59. try
  60. {
  61. number = number + 13;
  62. }finally {
  63. stampedLock.unlockWrite(stamp);
  64. }
  65. System.out.println(Thread.currentThread().getName()+"\t"+"写线程结束修改");
  66. }
  67. }

2.执行结果

3.代码:  

//暂停6秒钟线程
        try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); }

  1. package com.ljf.thread.readwritelock.stamp;
  2. import java.util.concurrent.TimeUnit;
  3. import java.util.concurrent.locks.StampedLock;
  4. /**
  5. * @ClassName: StampLock
  6. * @Description: TODO
  7. * @Author: admin
  8. * @Date: 2023/10/01 21:26:47 
  9. * @Version: V1.0
  10. **/
  11. public class StampLock {
  12. static int number = 37;
  13. static StampedLock stampedLock = new StampedLock();
  14. public static void main(String[] args) {
  15. StampLock resource=new StampLock();
  16. new Thread(() -> {
  17. resource.tryOptimisticRead();
  18. },"readThread").start();
  19. //暂停2秒钟线程,读过程可以写介入,演示
  20. //try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
  21. //暂停6秒钟线程
  22. try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); }
  23. new Thread(() -> {
  24. System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
  25. resource.write();
  26. },"writeThread").start();
  27. }
  28. //乐观读,读的过程中也允许获取写锁介入
  29. public void tryOptimisticRead()
  30. {
  31. long stamp = stampedLock.tryOptimisticRead();
  32. int result = number;
  33. //故意间隔4秒钟,很乐观认为读取中没有其它线程修改过number值,具体靠判断
  34. System.out.println("4秒前stampedLock.validate方法值(true无修改,false有修改)"+"\t"+stampedLock.validate(stamp));
  35. for (int i = 0; i < 4; i++) {
  36. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
  37. System.out.println(Thread.currentThread().getName()+"\t"+"正在读取... "+i+" 秒" +
  38. "后stampedLock.validate方法值(true无修改,false有修改)"+"\t"+stampedLock.validate(stamp));
  39. }
  40. if(!stampedLock.validate(stamp))
  41. {
  42. System.out.println("有人修改过------有写操作");
  43. stamp = stampedLock.readLock();
  44. try
  45. {
  46. System.out.println("从乐观读 升级为 悲观读");
  47. result = number;
  48. System.out.println("重新悲观读后result:"+result);
  49. }finally {
  50. stampedLock.unlockRead(stamp);
  51. }
  52. }
  53. System.out.println(Thread.currentThread().getName()+"\t"+" finally value: "+result);
  54. }
  55. public void write()
  56. {
  57. long stamp = stampedLock.writeLock();
  58. System.out.println(Thread.currentThread().getName()+"\t"+"写线程准备修改");
  59. try
  60. {
  61. number = number + 13;
  62. }finally {
  63. stampedLock.unlockWrite(stamp);
  64. }
  65. System.out.println(Thread.currentThread().getName()+"\t"+"写线程结束修改");
  66. }
  67. }

4.结果

 2.3 Stampedlock的缺点

Stampedlock不支持重入,没有Re开头;stampedLock的悲观读锁和写锁不支持条件变量condition;使用stampedlock一定不要调用中断操作,即不要调用interrupt()方法。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/956212
推荐阅读
  

闽ICP备14008679号