多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

JUC 并发编程-StampedLock

JUC 并发编程-StampedLock ReentrantReadWriteLock『读写锁ReentrantReadWriteLock』并不是真正意义上的读写分离它只允许读读共存而读写和写写依然是互斥的大多实际场景是“读/读”线程间并不存在互斥关系只有读/写线程或写/写线程间的操作需要互斥的。因此引入 ReentrantReadWriteLock 。一个 ReentrantReadWriteLock 同时只能存在一个写锁但是可以存在多个读锁但不能同时存在写锁和读锁, 也即一个资源可以被多个读操作访问或一个写操作访问但两者不能同时进行。只有在读多写少情境之下读写锁才具有较高的性能体现。读写互斥写锁和读锁是互斥的这里的互斥是指线程间的互斥当前线程可以获取到写锁又获取到读锁但是获取到了读锁不能继续获取写锁这是因为读写锁要保持写操作的可见性。因为如果允许读锁在被获取的情况下对写锁的获取那么正在运行的其他读线程无法感知到当前写线程的操作。因此分析 读写锁 ReentrantReadWriteLock会发现它有个潜在的问题读锁全完写锁有望写锁独占读写全堵如果有线程正在读写线程需要等待读线程释放锁后才能获取写锁, 即 ReadWriteLock 读的过程中不允许写只有等待线程都释放了读锁当前线程才能获取写锁也就是写入必须等待这是一种悲观的读锁o(╥﹏╥)o人家还在读着那你先别去写省的数据乱。后续讲解 StampedLock 时再详细展开分析 StampedLock(后面详细讲解)会发现它改进之处在于读的过程中也允许获取写锁介入(相当牛B读和写两个操作也让你“共享”(注意引号))这样会导致我们读的数据就可能不一致所以需要额外的方法来判断读的过程中是否有写入这是一种乐观的读锁O(∩_∩)O哈哈~。显然乐观锁的并发效率更高但一旦有小概率的写入导致读取的数据不一致需要能检测出来再读一遍就行。后续讲解 StampedLock 时再详细展开锁降级锁降级: 可以将写锁降级为读锁, 锁降级是为了让当前线程感知到数据的变化,保证数据的可见性StampedLockStampedLock 是什么StampedLock 是JDK1.8 中新增的一个读写锁, 也是对 JDK 1.5 中的ReentrantReadWriteLock的优化.StampedLock 叫 邮戳锁.代表了锁的状态, 当 stamp 返回零时, 表示线程获取锁失败. 并且, 当释放锁或者转换锁的时候, 都要传入最除获取难度 stamp 值.它是由锁饥饿问题引出的锁饥饿问题ReentrantReadWriteLock 实现了读写分离但是一旦读操作比较多的时候想要获取写锁就变得比较困难了假如当前1000个线程999个读1个写有可能999个读取线程长时间抢到了锁那1个写线程就悲剧了.因为当前有可能会一直存在读锁而无法获得写锁根本没机会写o(╥﹏╥)o使用公平锁可以缓解 饥饿锁 问题, 但是会造成性能的下降.StampedLock 锁闪亮登场ReentrantReadWriteLock 的读锁被占用的时候其他线程尝试获取写锁的时候会被阻塞。但是StampedLock 采取乐观获取锁后其他线程尝试获取写锁时不会被阻塞这其实是对读锁的优化所以在获取乐观读锁后还需要对结果进行校验。StampedLock 特点所有获取锁的方法, 都返回一个 stamp , stamp 为零表示获取失败, 其余表示成功;所有释放锁的方法, 都需要一个 stamp , 这个 stamp 必须和成功获取锁时得到的 stamp 一致;StampedLock 是不可重入的!StampedLock 的三种访问模式乐观锁演示读的过程也允许获取写锁的接入.importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.StampedLock;publicclassStampedLockDemo{staticintnumber37;staticStampedLockstampedLocknewStampedLock();publicvoidwrite(){longstampstampedLock.writeLock();System.out.println(Thread.currentThread().getName()\t写线程准备修改);try{numbernumber13;}catch(Exceptione){e.printStackTrace();}finally{stampedLock.unlockWrite(stamp);}System.out.println(Thread.currentThread().getName()\t写线程结束修改);}//悲观读publicvoidread(){longstampstampedLock.readLock();System.out.println(Thread.currentThread().getName()\t come in readlock block,4 seconds continue...);//暂停几秒钟线程for(inti0;i4;i){try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()\t 正在读取中......);}try{intresultnumber;System.out.println(Thread.currentThread().getName()\t 获得成员变量值resultresult);System.out.println(写线程没有修改值因为 stampedLock.readLock()读的时候不可以写读写互斥);}catch(Exceptione){e.printStackTrace();}finally{stampedLock.unlockRead(stamp);}}//乐观读publicvoidtryOptimisticRead(){longstampstampedLock.tryOptimisticRead();intresultnumber;//间隔4秒钟我们很乐观的认为没有其他线程修改过number值实际靠判断。System.out.println(4秒前stampedLock.validate值(true无修改false有修改)\tstampedLock.validate(stamp));for(inti1;i4;i){try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println(Thread.currentThread().getName()\t 正在读取中......i秒后stampedLock.validate值(true无修改false有修改)\tstampedLock.validate(stamp));}if(!stampedLock.validate(stamp)){System.out.println(有人动过--------存在写操作);stampstampedLock.readLock();try{System.out.println(从乐观读 升级为 悲观读);resultnumber;System.out.println(重新悲观读锁通过获取到的成员变量值resultresult);}catch(Exceptione){e.printStackTrace();}finally{stampedLock.unlockRead(stamp);}}System.out.println(Thread.currentThread().getName()\t finally value: result);}publicstaticvoidmain(String[]args){StampedLockDemoresourcenewStampedLockDemo();newThread(()-{resource.read();//resource.tryOptimisticRead();},readThread).start();// 2秒钟时乐观读失败6秒钟乐观读取成功resource.tryOptimisticRead();修改切换演示//try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); }newThread(()-{resource.write();},writeThread).start();}}StampedLock 的缺点不支持重入.StampedLock 的悲观读锁和写锁都不支持条件变量 (Condation).使用 StampedLock 一定不要调用中断操作, 即不要调用 intertrupt() 方法.
返回列表