面试题
1.你说你用过读写锁,锁饥饿问题是什么?
2.有没有比读写锁更快的锁?
3.StampedLock知道吗?(邮戳锁/票据锁)
4.ReentrantReadWriteLock有锁降级机制策略你知道吗?
在并发编程领域,有多线程进行提升整体性能,但是却引入了共享数据安全性问题。基本就是无锁编程下的单线程操作,有互斥同步锁操作,但是性能不高,并且同一时刻只有一个线程可以操作资源类。但是对于大多数常见下,都是读操作多,写操作少,那么可以利用将锁的粒度进行细化,进而分化出读锁/写锁。也就是syn/ReentrantLock的升级版本ReentrantReadWriteLock。
读写锁
public class LockDemo {private static Map<Integer,Integer> cacheMap = new HashMap<>();private Lock lock = new ReentrantLock();private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();public void write(Integer key, Integer value) {readWriteLock.writeLock().lock();try {System.out.println("当前"+key+"正在写入");Thread.sleep(500);cacheMap.put(key,value);System.out.println("当前"+key+"写入完毕");} catch (Exception e) {e.fillInStackTrace();} finally {readWriteLock.writeLock().unlock();}}public void read(Integer key) {readWriteLock.readLock().lock();try {System.out.println("当前"+key+"正在读取");cacheMap.get(key);System.out.println("当前"+key+"读取完毕");} catch (Exception e) {e.fillInStackTrace();} finally {readWriteLock.readLock().unlock();}}public static void main(String[] args) {LockDemo lockDemo = new LockDemo();for (int i = 0; i < 10; i++) {int finalI = i;new Thread(()->{lockDemo.write(finalI, finalI);}).start();}for (int i = 0; i < 10; i++) {int finalI = i;new Thread(()->{lockDemo.read(finalI);}).start();}}}
从执行结果来看,读锁不互斥。读取1的时候,还可以读取别的数据。
锁降级
锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性
public class LockDemo2 {public static void main(String[] args) {ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();readLock.lock();System.out.println("读取数据");readLock.unlock();writeLock.lock();System.out.println("写入数据");readLock.lock();System.out.println("读取数据");writeLock.unlock();readLock.unlock();}}
调整顺序之后,读锁不能升级为写锁,但是写锁可以降级为读锁。
存在的问题
为了解决读写锁,锁饥饿的问题,解决方案有两个,1.通过使用公平锁来解决,但是公平锁会牺牲系统吞吐量为代价的。
2.使用stampedLock邮戳锁。
stampedlock
代表了锁的状态。当stamp返回零时,表示线程获取锁失败。并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值。
因为读写lock,虽然可以提升一定的性能,但是因为存在饥饿的问题,读写互斥。而邮戳锁是一种乐观锁,使用类似版本校验的机制,选判断数据有没有修改,没有修改直接读取,有修改则升级为悲观读取。其实是一种权衡。
StampedLock有三种访问模式
①Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
③**Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,**支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式
StampedLock的缺点
- StampedLock 不支持重入,没有Re开头
- StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。
- 使用 StampedLock一定不要调用中断操作,即不要调用interrupt() 方法
- 如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()
小结
本篇主要介绍了读写锁,以及读写锁的锁饥饿问题,为了进一步提升性能引入了邮戳锁,但是邮戳锁不支持重入和中断等。