简介
它是java8在java.util.concurrent.locks新增的一个API。
ReentrantReadWriteLock 在沒有任何读写锁时,才可以取得写入锁,这可用于实现了悲观读取(Pessimistic Reading),即如果执行中进行读取时,经常可能有另一执行要写入的需求,为了保持同步,ReentrantReadWriteLock 的读取锁定就可派上用场。
然而,如果读取执行情况很多,写入很少的情况下,使用 ReentrantReadWriteLock 可能会使写入线程遭遇饥饿(Starvation)问题,也就是写入线程吃吃无法竞争到锁定而一直处于等待状态。
StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。
所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!
悲观读
悲观读这种方式跟之前ReentrantReadWriteLock基本没有区别,但比ReentrantReadWriteLock效率要高。
创建10个线程,9个读,1个写:
/**
* 悲观读
* 这种形式跟ReentrantReadWriteLock基本没有区别
*/
public class StampedLockTest1 {
private static final StampedLock stampedLock = new StampedLock();
private static final List<Long> DATA = new ArrayList<>();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
Runnable readRunnable = ()->{
for (;;){
read();
}
};
Runnable writeRunnable = ()->{
for (;;){
write();
}
};
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(writeRunnable);
}
private static void read() {
long stamp = -1;
try {
stamp = stampedLock.readLock();
Optional.of(
DATA.stream().map(String::valueOf).collect(Collectors.joining("#", "R-", ""))
).ifPresent(System.out::println);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
stampedLock.unlockRead(stamp);
}
}
private static void write() {
long stamp = -1;
try {
stamp = stampedLock.writeLock();
DATA.add(System.currentTimeMillis());
System.out.println("=======写=======");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
stampedLock.unlockWrite(stamp);
}
}
}
乐观读
/**
* 乐观读
*/
public class StampedLockTest2 {
private static final StampedLock stampedLock = new StampedLock();
private static final List<Long> DATA = new ArrayList<>();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
Runnable readRunnable = ()->{
for (;;){
read();
}
};
Runnable writeRunnable = ()->{
for (;;){
write();
}
};
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(readRunnable);
executor.submit(writeRunnable);
}
private static void read() {
/**tryOptimisticRead是一个乐观的读,使用这种锁的读不阻塞写
* 每次读的时候得到一个当前的stamp值(类似时间戳的作用)*/
long stamp = stampedLock.tryOptimisticRead();
Optional.of(
DATA.stream().map(String::valueOf).collect(Collectors.joining("#", "R-", ""))
).ifPresent(System.out::println);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**如果读取的时候发生了写,则stampedLock的stamp属性值会变化,此时需要重读,
* 再重读的时候需要加读锁(并且重读时使用的应当是悲观的读锁,即阻塞写的读锁)
* 当然重读的时候还可以使用tryOptimisticRead,此时需要结合循环了,即类似CAS方式
* 读锁又重新返回一个stampe值*/
if (!stampedLock.validate(stamp)){//重读
try {
stamp = stampedLock.readLock();
Optional.of(
DATA.stream().map(String::valueOf).collect(Collectors.joining("#", "R-", ""))
).ifPresent(System.out::println);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
stampedLock.unlockRead(stamp);
}
}
}
private static void write() {
long stamp = -1;
try {
stamp = stampedLock.writeLock();
DATA.add(System.currentTimeMillis());
System.out.println("=======写=======");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
stampedLock.unlockWrite(stamp);
}
}
}
通过上述例子打印结果可以看出:当悲观读时,基本是读9次,才出现写(因为在读的时候,一个睡眠一秒,在这一秒内,其它的读线程可以获取锁,而写线程是无法获取锁的。只有当所有读释放锁了,这时写线程才能去抢锁)。乐观读时,是不固定的(即使读线程没有释放锁,写线程还是可以获取到锁)。
注意:当读线程远大于写线程时,乐观读效率才能体现。