首页 > 代码库 > Java 8的StampedLock
Java 8的StampedLock
没有人喜欢同步的代码,这会降低你的应用的吞吐量等性能指标,最坏的时候会挂起死机,但是即使这样你也没有太多选择。
很多理论和模式来实现多线程同步访问一个资源, 其中最著名常用的是读写锁ReadWriteLock,它是通过堵塞来降低多线程消费一个资源引起的竞争,理论上听起来不错,但是在现实中锁意味着性能慢,特别是有大量写线程的情况下。
Java 8 引入了一个新的读写锁叫StampedLock. 不仅这个锁更快,而且它提供强大的乐观锁API,这意味着你能以一个较低的代价获得一个读锁, 在这段时间希望没有写操作发生,当这段时间完成后,你可以查询一下锁,看是否在刚才这段时间是否有写操作发生?然后你可以决定是否需要再试一次 或升级锁或放弃。
通常我们的同步锁如下代码:
synchronized(this)
// do operation
}
Java 6提供的ReentrantLock代码如下:
rwlock.writeLock().lock();
try {
// do operation
} finally {
rwlock.writeLock().unlock();
}
ReentrantReadWriteLock, ReentrantLock 和synchronized锁都有相同的内存语义,不管怎么说synchronized代码要更容易书写些。ReentrantLock的代码必须严格按照这种写法,否则就会造成严重的问题。
StampedLock要比ReentrantReadWriteLock更加廉价,也就是消耗比较小。
StampedLock控制锁有三种模式,一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。
下面是StampedLock的代码应用:
public class BankAccountWithStampedLock {
private final StampedLock lock = new StampedLock();
private double balance;
public void deposit(double amount) {
long stamp = lock.writeLock();
try {
balance = balance + amount;
} finally {
lock.unlockWrite(stamp);
}
}
public double getBalance() {
long stamp = lock.readLock();
try {
return balance;
} finally {
lock.unlockRead(stamp);
}
}
}
如果我们要实现跨字段的不变性访问,比如下面MyPoint 中x 和y两个变量必须同时增加,这就需要排他锁:
public class MyPoint {
private double x, y;
private final StampedLock sl = new StampedLock();
// method is modifying x and y, needs exclusive lock
public void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
对于有条件的状态改变,需要将读锁转为写锁,如下代码:
public void moveIfAt(double oldX, double oldY, double newX, double newY) {
long stamp = sl.readLock(); //获得一个读悲观锁
try {
while (x == oldX && y == oldY) { //循环,检查当前状态是否符合
long writeStamp = sl.tryConvertToWriteLock(stamp);//将读锁转为写锁
if (writeStamp != 0L) {//这是确认转为写锁是否成功
stamp = writeStamp; //如果成功 替换票据
x = newX; y = newY; //进行状态改变
break;
} else { //如果不能成功转换为写锁
sl.unlockRead(stamp);//我们显式释放读锁
stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试
}
}
} finally {
sl.unlock(stamp); //释放读锁或写锁
}
}
以上是悲观读锁案例,下面看看乐观读锁案例:
public 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);
}
http://www.jdon.com/idea/java/java-8-stampedlock.html
Java 8的StampedLock