StampedLock 支持的三种锁模式

简介: StampedLock 支持的三种锁模式

用锁最佳实践

1、永远只在更新对象的成员变量时加锁

2、永远只在访问可变的成员变量时加锁

3、永远不在调用其他对象的方法时加锁

1.写锁

2.悲观读锁

3.乐观读

相对于ReadWriteLock 有啥区别

ReadWriteLock两种模式是:读锁, 写锁。而stampedLock 支持三种,分别为 写锁,乐观读锁,乐观读。写锁和悲观读锁都是互斥的,不同的是, 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);
 }

StampedLock 之所以能比ReadWriteLock的性能好,关键是 stampedLock 支持乐观读的方式,ReadWriteLock支持多个线程同时读,但是多个线程读的时候,所有写的操作会被阻塞;而StampedLock提供的乐观读,是应许一个线程获取写锁的,就是说不是所有的写锁操作都是被阻塞。注意这里,我们用的是“乐观读”这个词,而不是“乐观读锁”,是要提醒你,乐观读这个操作是无锁的,所以相比较 ReadWriteLock 的读锁,乐观读的性能更好一些。

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(!s1.validate(stamp)){
            // 升级为悲观读锁
            stamp = sl.readLock();
            try{
                curX = x;
                curY = y;
            }finally{
                // 释放悲观读锁
                sl.unLockRead(stamp);
            }
        }
        return Math.sqrt(
            curX * curY + curX + curY;
        )
    }
}

StampedLock 使用注意事项

1、对于读多写少的场景StampedLock的性能很好,简单的应用场景基本上可以替代ReadWriteLock,但是StampedLock的功能仅仅是ReadWriteLock的子集,在使用的时候,还是有几个地方需要注意一下

2、StampedLock 命名上并没有增加Reentrant, 想必你已经知道,StampedLock 应该是不可重入的。事实上,的确是这样的,StampedLock 不支持重入。这个是在使用中必须要特别注意的。另外,StampedLock 悲观读锁、写锁都不支持条件变量,这个也需要你注意。

使用注意

那就是:如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。例如下面的代码中,线程 T1 获取写锁之后将自己阻塞,线程 T2 尝试获取悲观读锁,也会阻塞;如果此时调用线程 T2 的 interrupt() 方法来中断线程 T2 的话,你会发现线程 T2 所在 CPU 会飙升到 100%。

final StampedLock = 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);
}
目录
相关文章
|
6月前
多线程并发锁的方案—原子操作
多线程并发锁的方案—原子操作
|
5月前
|
存储 安全 算法
深入探索Java中的MarkWord与锁优化机制——无锁、偏向锁、自旋锁、重量级锁
深入探索Java中的MarkWord与锁优化机制——无锁、偏向锁、自旋锁、重量级锁
140 1
|
6月前
|
存储 安全 Java
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
77 1
12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
|
6月前
|
Java 编译器 程序员
synchronized 原理(锁升级、锁消除和锁粗化)
synchronized 原理(锁升级、锁消除和锁粗化)
|
存储 Java
synchronized锁升级原理
synchronized锁升级原理
263 1
synchronized锁升级原理
|
算法 Java 编译器
常见的锁策略和synchronized的锁机制
常见的锁策略和synchronized的锁机制
134 0
常见的锁策略和synchronized的锁机制
锁消除、锁粗化、锁升级区别与联系
锁消除、锁粗化、锁升级区别与联系
锁消除、锁粗化、锁升级区别与联系
|
安全 Java 调度
【Java 并发编程】线程锁机制 ( 锁的四种状态 | 无锁状态 | 偏向锁 | 轻量级锁 | 重量级锁 | 锁竞争 | 锁升级 )
【Java 并发编程】线程锁机制 ( 锁的四种状态 | 无锁状态 | 偏向锁 | 轻量级锁 | 重量级锁 | 锁竞争 | 锁升级 )
256 0
【Java 并发编程】线程锁机制 ( 锁的四种状态 | 无锁状态 | 偏向锁 | 轻量级锁 | 重量级锁 | 锁竞争 | 锁升级 )