[Java concurrency tool class-mutual exclusion] StampedLock (a lock with higher performance than read-write lock)

2. StampedLock

2.1 Three lock modes supported by StampedLock

The three modes are: write lock, pessimistic read lock, and optimistic read .

  • Among them, its write lock, pessimistic read lock and ReadWriteLock write lock, read lock semantics are similar: both allow multiple threads to obtain pessimistic read locks at the same time, only allow one thread to obtain write locks, while write locks and read locks are mutually exclusive of. The difference is that: write lock in StampedLock, pessimistic read lock will return a stamp after the lock is successfully locked; then when unlocking, you need to pass in this stamp. The relevant code is as follows:
final StampedLock sl =  new StampedLock();  
long stamp = sl.readLock();		// 获取/释放悲观读锁示意代码
try {
  //省略业务相关代码
} finally {
  sl.unlockRead(stamp);
}

long stamp = sl.writeLock();// 获取/释放写锁示意代码
try {
  //省略业务相关代码
} finally {
  sl.unlockWrite(stamp);
}
  • Optimistic reading: The reason why StampedLock is better than ReadWriteLock is that the key is that StampedLock supports optimistic reading (optimistic read operations are lock-free)!
    ReadWriteLock supports multiple threads reading, but when multiple threads are reading, all the writing threads are blocked, and the optimistic reading provided by StampedLock, when multiple threads are reading, allows one thread to obtain a write lock!
2.2 StampedLock principle of optimistic reading

The following code is an official example from the Java SDK and has been slightly modified.

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 Math.sqrt(
      curX * curX + curY * curY);
  }
}

In the above code, if there is a write operation during the optimistic read, the optimistic read will be upgraded to a pessimistic read lock, otherwise a loop will be required to repeatedly execute the optimistic read until there is no write operation during the period, the loop will waste cpu resources, so it is upgraded to a pessimistic read Lock, reasonable!

Note : In the above code, there is no write operation during the validate check, but if the write operation is performed before the Math.sqrt and after the check, the synchronization of the data cannot be judged in time, and only the correctness and consistency can be guaranteed. This is something I do n’t understand. If any teacher understands, I hope to write a message in the comment area.

In fact, when it comes to optimistic reading, you may think of the database transaction MVCC (multi-version concurrency control). In fact, the two are somewhat similar. According to the MVCC principle, each modification of the data corresponds to a version number. There is no only modification of the data or version number. .
When the database transaction is started, a snapshot will be taken of the database, and all read and write operations in the transaction will be based on this snapshot in the future. When the transaction is committed, the database data will be written at the time, and the write will be verified. Whether the input data version is consistent with the version number when reading the data. If they are consistent, that is, all the read and written data have not changed during the execution of the transaction, and the data is submitted. If they are inconsistent, they have changed, indicating that during the execution of the transaction, Other transactions were submitted, conflicts occurred, and they could not be submitted.

The idea of ​​optimistic reading is that nothing is wrong, there is no write operation, check if there is a write operation, no, operate the data; yes, upgrade the pessimistic lock.
The idea of ​​MVCC is that nothing is wrong, there is no write operation, make a copy, modify the copy first, when writing to the database, (check the version to see if there are other write operations, yes, can't commit transactions, no, commit transactions.) Atomic operations

2.3 StampedLock considerations

StampedLock performance is very good for the scenario of reading more and writing less. Simple application scenarios can basically replace ReadWriteLock, but the function of StampedLock is only a subset of ReadWriteLock. When using it, there are still a few places to note.

  1. StampedLock does not add Reentrant in its naming, so StampedLock does not support reentry.
  2. In addition, StampedLock's pessimistic read locks and write locks do not support condition variables, this also requires your attention.
  3. If the thread is blocked on the readLock () or writeLock () of StampedLock, calling the interrupt () method of the blocked thread at this time will cause the CPU to soar. Therefore, when using StampedLock, you must not call interrupt operations. If you need to support the interrupt function, you must use the interruptible pessimistic read lock readLockInterruptibly () and write lock writeLockInterruptibly (). This rule must be remembered clearly.
    For example, the following code example (interrupt blocking readLock causes the CPU to soar):
final StampedLock lock = new StampedLock();
Thread T1 = new Thread(()->{
  // 获取写锁
  lock.writeLock();
  LockSupport.park(); // 永远阻塞在此处,不释放写锁
});
T1.start();
// 保证T1获取写锁
Thread.sleep(100);
Thread T2 = new Thread(()->
  lock.readLock()  //阻塞在悲观读锁
);
T2.start();
Thread.sleep(100);// 保证T2阻塞在读锁
T2.interrupt();//中断线程T2//会导致线程T2所在CPU飙升
T2.join();
2.4 Standard StampedLock read-write template

It is recommended that you use StampedLock according to the following template in your actual work.

  • StampedLock read template:
final StampedLock sl =  new StampedLock();
// 乐观读
long stamp =  sl.tryOptimisticRead();
......// 读入方法局部变量
// 校验stamp
if (!sl.validate(stamp)){
  // 升级为悲观读锁
  stamp = sl.readLock();
  try {   
    ..... // 读入方法局部变量
  } finally {
    //释放悲观读锁
    sl.unlockRead(stamp);
  }
}
//使用方法局部变量执行业务操作
......
  • StampedLock write template:
long stamp = sl.writeLock();
try {
  ......// 写共享变量
} finally {
  sl.unlockWrite(stamp);
}

Reference: Geek Time
More: Deng Xin

Published 34 original articles · Likes0 · Visits 1089

Guess you like

Origin blog.csdn.net/qq_42634696/article/details/105135368