18. StampedLock:有没有比读写锁更快的锁?- 并发工具类

StampedLock 性能比读写锁更好

1. StampedLock 支持的三种锁模式

三种模式:写锁、悲观读锁和乐观读。其中,写锁、悲观读锁的语义和 ReadWriteLock 的写锁、读锁的语义非常类似,允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的。不同的是:StampedLock 里的写锁和悲观读锁加锁成功之后,都会返回一个 stamp;然后解锁的时候,需要传入这个 stamp。相关的示例代码如下。

final StampedLock sl =  new StampedLock();
  
// 获取 / 释放悲观读锁示意代码
long stamp = sl.readLock();
try {
  // 省略业务相关代码
} finally {
  sl.unlockRead(stamp);
}
 
// 获取 / 释放写锁示意代码
long stamp = sl.writeLock();
try {
  // 省略业务相关代码
} finally {
  sl.unlockWrite(stamp);
}

ReadWriteLock 支持多个线程同时读,但是当多个线程同时读的时候,所有的写操作会被阻塞;而 StampedLock 提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都被阻塞。乐观读这个操作是无锁的

class Point {
	private int x, y;
	final StampedLock sl = new StampedLock();

	// 计算到原点的距离
	int distanceFromOrigin() {
		// 乐观读
		long stamp = sl.tryOptimisticRead();
		// 读入局部变量,
		// 读的过程数据可能被修改
		int curX = x, curY = y;
		// 判断执行读操作期间,
		// 是否存在写操作,如果存在,
		// 则 sl.validate 返回 false
		if (!sl.validate(stamp)) {
			// 升级为悲观读锁
			stamp = sl.readLock();
			try {
				curX = x;
				curY = y;
			} finally {
				// 释放悲观读锁
				sl.unlockRead(stamp);
			}
		}
		return Math.sqrt(curX * curX + curY * curY);
	}
}

tryOptimisticRead()就是乐观读,读取x,y还要验证是否被改变了,通过validate(stamp)验证,如果被改变了,升级为悲观读锁。

2. 进一步理解乐观读

跟数据库的乐观锁类似。数据库的乐观锁是通过增加数值型字段version实现。

3. StampedLock 使用注意事项

  • StampedLock 的功能仅仅是 ReadWriteLock 的子集;
  • StampedLock 不支持重入;
  • StampedLock 的悲观读锁、写锁都不支持条件变量
  • 如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。例如下面的代码中,线程 T1 获取写锁之后将自己阻塞,线程 T2 尝试获取悲观读锁,也会阻塞;如果此时调用线程 T2 的 interrupt() 方法来中断线程 T2 的话,你会发现线程 T2 所在 CPU 会飙升到 100%。
final StampedLock lock  = new StampedLock();
	Thread T1 = new Thread(()->{
	  // 获取写锁
	  lock.writeLock();
	  // 永远阻塞在此处,不释放写锁
	  LockSupport.park();
	});
	T1.start();
	// 保证 T1 获取写锁
	Thread.sleep(100);
	Thread T2 = new Thread(()->
	  // 阻塞在悲观读锁
	  lock.readLock()
	);
	T2.start();
	// 保证 T2 阻塞在读锁
	Thread.sleep(100);
	// 中断线程 T2
	// 会导致线程 T2 所在 CPU 飙升
	T2.interrupt();
	T2.join();

使用 StampedLock 一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly() 和写锁 writeLockInterruptibly()。

StampedLock 读模板:

final StampedLock sl = 
  new StampedLock();
 
// 乐观读
long stamp = 
  sl.tryOptimisticRead();
// 读入方法局部变量
......
// 校验 stamp
if (!sl.validate(stamp)){
  // 升级为悲观读锁
  stamp = sl.readLock();
  try {
    // 读入方法局部变量
    .....
  } finally {
    // 释放悲观读锁
    sl.unlockRead(stamp);
  }
}
// 使用方法局部变量执行业务操作
......

StampedLock 写模板:

long stamp = sl.writeLock();
try {
  // 写共享变量
  ......
} finally {
  sl.unlockWrite(stamp);
}
发布了97 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39530821/article/details/102646900
18.