StampedLock的简单用法

StampedLock是JDK1.8新引入的锁机制,可以简单的理解为读写锁的改进版本。我们知道读写锁可以让读和读之间完全并发,但是读和写之间是有阻塞的。StampedLock使用了一种乐观锁的读策略,这种机制类似于无锁的概念,使得获取锁的过程中不会阻塞写线程。

简单例子

下面给出一个JDK官方给的Demo:

public class Point {

	private double x, y;
	private final StampedLock sl = new StampedLock();

	void move(double deltaX, double deltaY) {
		long stamp = sl.tryWriteLock();
		try {
			x += deltaX;
			y += deltaY;
		} finally {
			sl.unlockWrite(stamp);
		}
	}

	double distanceFromOrigin() {
		long stamp = sl.tryOptimisticRead();
		double currentX = x, currentY = y;
		if (!sl.validate(stamp)) {
			stamp = sl.readLock();
			try {
				currentX = x;
				currentY = y;
			} finally {
				sl.unlockRead(stamp);
			}
		}
		return Math.sqrt(currentX * currentX + currentY * currentY);
	}

}

上面是一个点移动的一个抽象。首先对于move()操作,加锁和解锁的过程和普通的Lock类似。对于distanceFromOrigin(),首先会使用tryOptimisticRead()加一个乐观读锁,此时会返回一个stamp数值。获取到X和Y的值之后,去验证其有效性,如果尚未被修改过,那么读取成功,计算结果返回。如果验证失败,那么会加读锁。

在这个例子中,使用的锁升级,里面调用了readLock()方法去操作。这个操作会有一个循环的CAS操作,直到获取到锁为止。

StampedLock的缺陷

在使用StampedLock的过程中,如果API使用的不当,则有可能造成CPU占用率过高。

// 获取读锁(阻塞,不响应中断)
long readLock();
// 获取读锁(立即)
long tryReadLock();
// 限时获取读锁(响应中断)
long tryReadLock(long time, TimeUnit unit);
// 获取读锁(阻塞,响应中断)
long readLockInterruptibly();

StampedLock在使用readLock()的时候,使用的是Unsafe.park()函数。park()在遇到线程中断时,会直接返回。这就导致阻塞在park()上面的线程被中断后,会再次进入循环,从而导致CPU的大量占用。以下是事例代码:

public class StampedLockCPUDemo {
	static Thread[] holdCpuThreads = new Thread[3];
	static final StampedLock lock = new StampedLock();

	public static void main(String[] args) throws InterruptedException {
		new Thread() {
			@Override
			public void run() {
				long readLong = lock.writeLock();
				LockSupport.parkNanos(600000000000L);
				lock.unlockWrite(readLong);
			}
		}.start();
		Thread.sleep(100);
		for (int i = 0; i < 3; ++i) {
			holdCpuThreads[i] = new Thread(new HoldCPUReadThread());
			holdCpuThreads[i].start();
		}
		Thread.sleep(10000);
		// 线程中断后,会占用CPU
		for (int i = 0; i < 3; ++i) {
			holdCpuThreads[i].interrupt();
		}
	}

	private static class HoldCPUReadThread implements Runnable {
		@Override
		public void run() {
			long lockr = lock.readLock();
			System.out.println(Thread.currentThread().getName() + "获得读锁");
			lock.unlockRead(lockr);
		}
	}

}

笔者在自己的电脑上面测试时,CPU的占用率如下:


参考:《Java高并发程序设计》