赞
踩
读写锁是指ReentrantReadWriteLock类
该类能被多个读线程访问或者一个写线程访问,但是不能同时存在读写线程
其特点主要是:一体两面、读写互斥、读读共享
有线程在写的时候,其他线程不能读;有线程在读的时候,其他线程不能写
第一阶段:无锁
在无锁的阶段,会导致数据大乱,多个线程写入数据,导致出现脏数据
第二阶段:synchronized 与 Lock接口(ReentrantLock类)
这两个的出现,使得线程有序,且能保持数据一致性
无论多少个线程过来,不管读还是写,每次都是一个
每次线程都是只有一个,所以会导致多个线程想读也只能一个个线程读,效率比较慢,因为读之间应该是可以共享的
第三阶段:ReadWriteLock接口(ReentrantReadWriteLock)
这个类不仅可以读写互斥,并实现了读读共享,多个线程并发可以访问,大面积可以容许多个线程来读取
在读多写少的时候,可以使用读写锁
但是也有以下缺点:
- 写锁饥饿问题:比如有10W个线程是读,只有一个线程是写的时候,会导致写的线程一直抢占不到资源,出现写锁饥饿问题
- 会出现锁降级问题
第四阶段:邮戳锁StampedLock
读的时候也允许写锁的接入(读写两个操作也让你“共享”),这样会导致我们读的数据就可能不一致,所以需要额外的方法来判断写的操作是否有写入,这是一种乐观锁
虽然乐观锁并发效率更高,但是一栏有小概率的写入导致读取的数据不一致,需要能检测出来,再读一次即可
什么是锁降级
锁降级就是ReentrantReadWriteLock将写入锁降级为读锁(就像Linux一样,写权限高于读权限),锁的严苛程度变强叫做升级,反之叫做降级
public class LockDownGradingDemo { public static void main(String[] args) { ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); writeLock.lock(); System.out.println("正在写入....."); readLock.lock(); System.out.println("正在读....."); writeLock.unlock(); readLock.unlock(); } }
但是注意的是,锁可以降级,但是不可以升级,且写锁和读锁是互斥的
锁降级:遵循获取写锁–》再获取读锁–》再释放写锁的次序,写锁能够降级成为读锁
锁降级是为了让当前线程感知到数据的变化,目的是保证数据的可见性
当读锁被使用时,如果有线程尝试获取写锁,该写线程会被阻塞。所以需要释放所有读锁,才能获取写锁
写锁和读锁是互斥的(这里的互斥是指线程间的互斥,当前线程可以获取到写钱锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保持写操作的可见性。因为,如果允许读锁在被获取的的情况下对写锁的获取,那么正在运行的其他读线程无法感知到当前写线程的操作。
因此,分析读写锁ReentrantReadWriteLock,会发现它有个潜在的问题:
读锁结束,写锁有望;写锁独占,读写全堵;
如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁
即ReentrantReadWriteLock读的过程中不允许写,只有等待线程都释放了读锁,当前线程才能获取写锁,也就是写入必须等待,这是一种悲观的读锁,o(一_-)o,人家还在读着那,你不先别去写,省的数据乱。
为什么要锁降级呢?
cacheValid是一个布尔值,默认为false,如果其他线程修改过这里的数据,那么会将cacheValid改为true
上面案例是首先先读取一次数据,然后接着获取写锁,获取写锁后,判断cacheValid是否有被修改过,如果没有那将data修改为某个值,然后将cacheValid设置为true。
接着锁降级获取读锁,获取读锁之后释放写锁
对数据进行读操作
这样做的好处有以下几点:
StampedLock优化的主要是ReentrantReadWriteLock出现的锁饥饿问题
stamp代表的是锁的状态。当stamp返回零时,表示线程获取锁失败。
并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值
回顾锁饥饿问题
ReentrantReadWriteLock实现读写分离,一旦读操作比较多的时候,想要获取写锁就会变得比较困难了,假如有1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那么1个写线程就悲剧了,因此当前有可能会一直存在读锁,而无法获得写锁
ReentrantReadWriteLock的读锁被占用的时候,其他线程尝试获取写锁的时候会被阻塞。
但是,StampedLock采取乐观获取锁后,其他线程尝试获取写锁时不会被阳塞,这其实是对读锁的优化,
所以,在获取乐观读锁后,还需要对结果进行校验。
/** * @Author: lrk * @Date: 2022/10/25 下午 8:52 * @Description: */ public class StampedLockDemo { static int number = 37; static StampedLock stampedLock = new StampedLock(); public void write() { long stamp = stampedLock.writeLock(); System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改"); try { number += 13; } finally { stampedLock.unlockWrite(stamp); } System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备结束"); } //悲观读,都没有完成的时候写锁无法获取锁 public void read() { long stamp = stampedLock.readLock(); System.out.println(Thread.currentThread().getName() + "\t" + "准备读,请等待....."); for (int i = 0; i < 4; i++) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "\t" + "正在读取中"); } try { int result = number; System.out.println(Thread.currentThread().getName() + "\t" + "获取成员变量值result: " + result); System.out.println("写线程没有修改成功,读锁的时候写锁无法接入,传统读写互斥"); } finally { stampedLock.unlockRead(stamp); System.out.println(Thread.currentThread().getName() + "\t" + "读线程准备结束"); } } public static void main(String[] args) { StampedLockDemo resource = new StampedLockDemo(); new Thread(() -> { resource.read(); }, "readThread").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { resource.write(); }, "writeThread").start(); } }
上面案例使用的Reading和Writing两种访问模式,实现的效果与ReentrantReadWriteLock的读写锁效果一样
public class StampedLockDemo { static int number = 37; static StampedLock stampedLock = new StampedLock(); public void write() { long stamp = stampedLock.writeLock(); System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改"); try { number += 13; } finally { stampedLock.unlockWrite(stamp); } System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备结束"); } public void read() { long stamp = stampedLock.readLock(); System.out.println(Thread.currentThread().getName() + "\t" + "准备读,请等待....."); for (int i = 0; i < 4; i++) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "\t" + "正在读取中"); } try { int result = number; System.out.println(Thread.currentThread().getName() + "\t" + "获取成员变量值result: " + result); System.out.println("写线程没有修改成功,读锁的时候写锁无法接入,传统读写互斥"); } finally { stampedLock.unlockRead(stamp); System.out.println(Thread.currentThread().getName() + "\t" + "读线程准备结束"); } } public void tryOptimisticRead() { long stamp = stampedLock.tryOptimisticRead(); int result = number; System.out.println("4s前stampedLock.validate方法值(true无修改,false有修改)" + "\t" + stampedLock.validate(stamp)); for (int i = 0; i < 4; i++) { try { TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "\t" + "正在读取...." + i + "秒" + stampedLock.validate(stamp) + "后stampedLock.validate方法值(true无修改,false有修改)" + "\t" + stampedLock.validate(stamp)); } catch (InterruptedException e) { throw new RuntimeException(e); } } if (!stampedLock.validate(stamp)) { System.out.println("有人修改过--------有写操作"); stamp = stampedLock.readLock(); try { System.out.println("从乐观读升级为悲观读"); result = number; System.out.println("重新悲观读result: " + result); } finally { stampedLock.unlockRead(stamp); } } System.out.println(Thread.currentThread().getName() + "\t" + "finally value:" + result); } public static void main(String[] args) { StampedLockDemo resource = new StampedLockDemo(); new Thread(() -> { resource.tryOptimisticRead(); }, "readThread").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { resource.write(); }, "writeThread").start(); } private static void extracted(StampedLockDemo resource) { new Thread(() -> { resource.read(); }, "readThread").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } new Thread(() -> { resource.write(); }, "writeThread").start(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。