Java1.8引入了一个新锁StampedLock,这个锁可以认为是ReadWriteLock的改进。
我们知道在ReadWriteLock中写和读是互斥的,也就是如果有一个线程在写共享变量的话,其他线程读共享变量都会阻塞。
StampedLock把读分为了悲观读和乐观读,悲观读就等价于ReadWriteLock的读,而乐观读在一个线程写共享变量时,不会被阻塞,乐观读是不加锁的。所以没锁肯定是比有锁的性能好,这样的话在大并发读情况下效率就更高了!
StampedLock的用法稍稍有点不同,在获取锁和乐观读时,都会返回一个stamp,解锁时需要传入这个stamp,在乐观读时是用来验证共享变量是否被其他线程写过。来看一下官方示例
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock(); //获取写锁
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp); //释放写锁
}
}
double distanceFromOrigin() { // A read-only method
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);
}
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock(); //获取读锁
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp); //升级为写锁
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
}
else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}
其上的操作在乐观读时,如果有写操作修改了共享变量则升级乐观读为悲观读锁,这样避免乐观读反复的循环等待写锁的释放,避免浪费CPU资源。所以在我们的使用StampedLock的时候,建议这样操作。
看起来好像StampedLock性能又比ReadWriteLock锁好,那是不是都可以用StampedLock抛弃ReadWriteLock?
并不是的,StampedLock不是可重入锁,所以不支持重入,并且StampedLock不支持条件变量,也就是没Condition。如果是线程使用writeLock()或者readLock()获得锁之后,线程还没执行完就被interrupt()的话,会导致CPU飙升....坑啊
我们来看下源码
public long readLock() {
long s = state, next; // bypass acquireRead on common uncontended case
return ((whead == wtail && (s & ABITS) < RFULL &&
U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
next : acquireRead(false, 0L)); //当CAS失败之后就会尝试申请锁,注意第一个参数是false
}
public long writeLock() {
long s, next; // bypass acquireWrite in fully unlocked case only
return ((((s = state) & ABITS) == 0L &&
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
next : acquireWrite(false, 0L)); //当CAS失败之后就会尝试申请锁,注意第一个参数是false
}
//就拿acquireWrite举例,acquireRead也是类似的。
private long acquireWrite(boolean interruptible, long deadline) {
WNode node = null, p;
for (int spins = -1;;) { // spin while enqueuing
//省略代码无数
if (interruptible && Thread.interrupted())
return cancelWaiter(node, node, true);
}
}
首先里面是个无限循环,然后 if (interruptible && Thread.interrupted())
已经得知调用的interruptible参数传入的是false,所以Thread.interrupted()
也不会执行到,也一定调用不到cancelWaiter
,所以就一直循环循环,CPU使用率就会涨涨涨。
所以如果要使用中断功能就得用readLockInterruptibly()
或者writeLockInterruptibly()
来获得锁。
如有错误欢迎指正!
个人公众号:yes的练级攻略