ReentrantReadWriteLock
读写锁定义:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。
它只允许读读
共存,而读写
和写写
依然互斥,大多实际场景读读
线程间并不存在互斥关系,只有读写
线程或写写
线程间的操作需要互斥。
一个ReentrantReadWriteLock同时只能存在一个写锁
但是可以存在多个读锁
,但不能同时存在写锁和读锁,即一个资源可以被多个读操作访问―或一个写操作访问,但两者不能同时进行。
特点
锁降级
ReentrantReadWriteLock锁降级:将写入锁降级为读锁,锁的严苛程度变强叫做升级,反之叫做降级。
- 如果同一个线程持有写锁,在没有释放写锁的情况下,它还可以继续获得读锁。这就是写锁的降级,降级成为读锁
- 将写锁降级为读锁:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级为读锁
如果释放了写锁,那么就完全转换为读锁。
读锁升级到写锁是不可能的
- 如果有线程在读,写线程需要等待读线程释放锁后才能获取锁
写锁和读锁互斥
写锁和读锁是互斥的(是指线程间的互斥,当前线程可以获取到写锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保持写操作的可见性,如果允许读锁在被获取的情况下对写锁的获取,那么正在运行的其他读线程无法感知到当前写线程的操作。
StampedLock
StampedLock(邮戳锁)是Java8新增的一个读写锁,是对读写锁ReentrantReadWriteLock的优化
stamp代表锁的状态。当stamp返回零时表示线程获取锁失败,并且当释放锁或者转换锁时都要传入最初获取的stamp值
锁饥饿
ReentrantReadWriteLock实现读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难
使用“公平”策略一定程度缓解该问题
1
| new ReentrantReadWriteLock(true);
|
使用“公平”策略是以牺牲系统吞吐量为代价的
StampedLock
ReentrantReadWriteLock读锁被占用时其他线程尝试获取写锁的时候会被阻塞,但StampedLock采取乐观获取锁后,其他线程尝试获取写锁时不会被阻塞
- 所有获取锁的方法都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功
- 所有释放锁的方法都需要一个邮戳(Stamp),Stamp必须是和成功获取锁时得到的Stamp一致
- StampedLock是不可重入的,如果一个线程已经持有写锁,再去获取写锁的话就会造成死锁
StampedLock三种访问模式
- Reading(读模式悲观):功能和ReentrantReadWriteLock的读锁类似
- Writing(写模式)︰功能和ReentrantReadWriteLbck的写锁类似
- Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式
栗子1(传统读写)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| public class StampedLocckDemo {
static int number = 88;
static StampedLock stampedLock = new StampedLock();
public void write() { long stamp = stampedLock.writeLock(); System.out.println(Thread.currentThread().getName() + " 写线程准备修改");
try { number = number + 12; } finally { stampedLock.unlockWrite(stamp); } System.out.println(Thread.currentThread().getName() + " 写线程结束修改"); }
public void read() {
long stamp = stampedLock.readLock(); System.out.println(Thread.currentThread().getName() + " 进入读锁,4s后继续...");
for (int i = 0; i < 4; i++) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 正在读取中..."); }
try {
int result = number; System.out.println(Thread.currentThread().getName() + " 获取成员变量result值:" + result); System.out.println(Thread.currentThread().getName() + " 写线程准备修改"); System.out.println("写线程没有修改成功读锁时无法介入"); } finally { stampedLock.unlockRead(stamp); }
}
public static void main(String[] args) {
StampedLocckDemo resource = new StampedLocckDemo();
new Thread(()->{ resource.read(); }, "readThread").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{ System.out.println(Thread.currentThread().getName() + " 进入"); resource.write(); }, "writeThread").start();
try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + " 获取成员变量result值:" + number); } }
readThread 进入读锁,4s后继续... readThread 正在读取中... writeThread 进入 readThread 正在读取中... readThread 正在读取中... readThread 正在读取中... readThread 获取成员变量result值:88 readThread 写线程准备修改 写线程没有修改成功读锁时无法介入 writeThread 写线程准备修改 writeThread 写线程结束修改 main 获取成员变量result值:100
|
栗子2(乐观读)
读过程也允许获取写锁介入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| public void tryOptimisticRead() { long stamp = stampedLock.tryOptimisticRead(); int result = number;
System.out.println("4s前validate()值(true无修改false有修改)" + stampedLock.validate(stamp)); for (int i = 0; i < 4; i++) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 正在读取中..." + i + "s后validate()值(true无修改false有修改)" + stampedLock.validate(stamp)); } 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() + " 最终结果为:result = " + result); }
}
public static void main(String[] args) {
StampedLocckDemo resource = new StampedLocckDemo();
new Thread(()->{ resource.tryOptimisticRead(); },"readThread").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{ System.out.println(Thread.currentThread().getName() + "来了..."); resource.write(); }, "writeThread").start(); }
4s前validate()值(true无修改false有修改)true readThread 正在读取中...0s后validate()值(true无修改false有修改)true writeThread来了... writeThread 写线程准备修改 writeThread 写线程结束修改 readThread 正在读取中...1s后validate()值(true无修改false有修改)false readThread 正在读取中...2s后validate()值(true无修改false有修改)false readThread 正在读取中...3s后validate()值(true无修改false有修改)false 有线程修改过了 从乐观读变更为悲观读... 重新进行悲观读后 result:100 readThread 最终结果为:result = 100
|
StampedLock 缺点
- StampedLock不支持重入
- StampedLock 的悲观读锁和写锁都不支持条件变量(Condition)
- 使用StampedLock一定不要调用中断操作,即不要调用
interrupt()
方法