stampedLock与ReentrantLock的区别
一,简单了解ReentrantLock
1,ReentrantLock是可重入锁,可重入锁可简单的理解为这样
int i = 0;
int j = 0;
void a (){
aa.lock();
i++;
b();
aa.unlock();
}
void b(){
bb.lock()
j++;
bb.unlock();
}
a方法已经被锁,但是a方法里的b方法依旧可以获取锁。
2,ReentrantLock 中的读锁与写锁,它们的特性为:读读共享,读写互斥,写写互斥
private ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.writeLock();
3,锁的降级:指的是将写锁降级为读锁的过程。
具体过程是:在拥有写锁的情况下同时去获取读锁,然后在释放写锁的过程。
4,应用场景:用于对数据比较敏感,需要在对数据修改之后,获取到修改后的值,并进行接下来的其他操作。
二,stampedLock
1,stampedLock是JDK1.8后引进的,相当于ReentrantLock的增强版,不过也有不同的地方。
与ReenstrantLock因读写互斥,在读时会阻塞写操作,因此而限制性能。在读多写少的情况下,读时不允许写操作,所有资源都被读锁占有,所以有可能出现线程饥饿。并且不支持锁的升级,也就是读锁升级为写锁。
2,stampedLock的特点:
(1)所有获取锁和释放锁的方法都需要返回一个邮戳,并且释放锁的邮戳要与获取时的一致。
private final StampedLock s1 = new StampedLock();
void move(double deltaX,double deltaY){
//stamp邮戳,获取锁
long stamp = s1.writeLock();
try {
x += deltaX;
y += deltaY;
}finally {
//释放锁
s1.unlockWrite(stamp);
}
}
(2)stampedLock不支持锁的重入
(3)使用有限次自旋,增加获取锁的几率,避免上下文切换带来的开销。(当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”。上下文切换并不廉价,是比较耗时的)
(4)使用乐观锁进行读,乐观锁读不阻塞写操作,悲观锁读会阻塞写操作。
(5)相比于ReenstrantLock,吞吐量大幅提升,
不过实现原理相对比较复杂。
3,使用stampedLock注意点:
如果使用乐观锁读,一定要判断返回的邮戳是否是一开始获得到邮戳,如果不是,则要用悲观读取去获取。
double distanceFormOrign() {
//使用乐观锁读
long stamp = s1.tryOptimisticRead();
double currentX = x, currentY = y;
//验证邮戳是不是一开始获取到,如果不是则进行悲观读去获取邮戳
if (!s1.validate(stamp)) {
//悲观读
stamp = s1.readLock();
try {
currentX = x;
currentY = y;
} finally {
s1.unlockRead(stamp);
}
}
return currentX+currentY;
}