zoukankan      html  css  js  c++  java
  • 多线程编程核心技术(十四)StampedLock

    在Java1.8之后提供了一种印章锁,性能上读写锁更快,并且支持乐观锁,悲观锁。

    其中,写锁、悲观读锁的语义和 ReadWriteLock 的写锁、读锁的语义非常类似,允许多个线程同时获取悲观读锁,但是只允许一个线程获取写锁,写锁和悲观读锁是互斥的。不同的是:StampedLock 里的写锁和悲观读锁加锁成功之后,都会返回一个 stamp;然后解锁的时候,需要传入这个 stamp

    StampedLock 的性能之所以比 ReadWriteLock 还要好,其关键是 StampedLock 支持乐观读的方式。ReadWriteLock 支持多个线程同时读,但是当多个线程同时读的时候,所有的写操作会被阻塞;而 StampedLock 提供的乐观读(无锁操作),是允许一个线程获取写锁的,也就是说不是所有的写操作都被阻塞。

     在 distanceFromOrigin() 这个方法中,首先通过调用 tryOptimisticRead() 获取了一个 stamp,这里的 tryOptimisticRead() 就是我们前面提到的乐观读。之后将共享变量 x 和 y 读入方法的局部变量中,不过需要注意的是,由于 tryOptimisticRead() 是无锁的,所以共享变量 x 和 y 读入方法局部变量时,x 和 y 有可能被其他线程修改了。因此最后读完之后,还需要再次验证一下是否存在写操作,这个验证操作是通过调用 validate(stamp) 来实现的

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

      如果执行乐观读操作的期间,存在写操作,会把乐观读升级为悲观读锁。这个做法挺合理的,否则你就需要在一个循环里反复执行乐观读,直到执行乐观读操作的期间没有写操作(只有这样才能保证 x 和 y 的正确性和一致性),而循环读会浪费大量的 CPU。升级为悲观读锁,代码简练且不易出错,建议你在具体实践时也采用这样的方法。

    这个乐观读和数据库里面的乐观锁思想是一样的,先进行读取然后进行判断,如何合理就直接返回。

    对于读多写少的场景 StampedLock 性能很好,简单的应用场景基本上可以替代 ReadWriteLock,但是 StampedLock 的功能仅仅是 ReadWriteLock 的子集,在使用的时候,还是有几个地方需要注意一下。另外StampedLock 不支持重入,StampedLock 的悲观读锁、写锁都不支持条件变量,这个也需要你注意(因为内部实现里while循环里面对中断的处理有点问题)。

    另外如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。使用 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);
    }
    

      

  • 相关阅读:
    SQL-W3School-高级:SQL CHECK 约束
    SQL-W3School-高级:SQL FOREIGN KEY 约束
    SQL-W3School-高级:SQL PRIMARY KEY 约束
    SQL-W3School-高级:SQL UNIQUE 约束
    ylbtech-SQL-W3School-高级:SQL NOT NULL 约束
    SQL-W3School-高级:SQL 约束(Contraints)
    SQL-W3School-高级:SQL CREATE TABLE 语句
    SQL-W3Chool-高级:SQL CREATE DATABASE 语句
    SQL-W3School-高级:SQL SELECT INTO 语句
    02:输出最高分数的学生姓名
  • 原文地址:https://www.cnblogs.com/SmartCat994/p/14210504.html
Copyright © 2011-2022 走看看