J.U.C-AQS框架同步组件之StampedLock乐观锁悲观锁

StampedLock java1.8提供的, 性能比ReadWriteLock好.

乐观锁

乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低(不是没有, 所以还要加锁, 区别于不加锁的乐观读),每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
  
java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

悲观锁

悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。java中的悲观锁就是Synchronized

    final StampedLock sl = new StampedLock();
 
    /**
     * 悲观读锁
     * 与ReadWriteLock的读锁相似
     * 允许多个线程进行读操作
     * 与写锁互斥
    **/
    //获取悲观锁
    long stamp = sl.readLock();
    try {
      //。。。。
    }finally {
         sl.unlockRead(stamp);
       }


    /**
     * 写锁
     * 与ReadWriteLock的写锁相似
     * 只允许一个线程进行写操作
     * 与读锁互斥
    **/
     //获取写锁
    long stamp1 = sl.writeLock();
    try{
        //。。。
    }finally {
        //释放写锁
        sl.unlockWrite(stamp1);
    }


    /**
     * 乐观读锁升级为悲观读
     * 锁的严苛程度变强叫做升级,反之叫做降级
    **/
    //获取乐观锁
     long stamp = sl.tryOptimisticRead();
     //判断执行读操作期间,是否存在写操作,如果存在则sl.validate返回false
    if (!sl.validate(stamp)){
        //升级为悲观锁
        stamp = sl.readLock();
        try{
            //。。。
        }finally {
            //释放悲观读锁
            sl.unlockRead(stamp);
        }

语法

final StampedLock sl = new StampedLock();
 
/**
 * 悲观读锁
 * 与ReadWriteLock的读锁相似
 * 允许多个线程进行读操作
 * 与写锁互斥
**/
//获取悲观锁
long stamp = sl.readLock();
try {
  //。。。。
}finally {
     sl.unlockRead(stamp);
   }


/**
 * 写锁
 * 与ReadWriteLock的写锁相似
 * 只允许一个线程进行写操作
 * 与读锁互斥
**/
 //获取写锁
long stamp1 = sl.writeLock();
try{
    //。。。
}finally {
    //释放写锁
    sl.unlockWrite(stamp1);
}


/**
 * 乐观读锁升级为悲观读
 * 锁的严苛程度变强叫做升级,反之叫做降级
**/
//获取乐观锁
 long stamp = sl.tryOptimisticRead();
 //判断执行读操作期间,是否存在写操作,如果存在则sl.validate返回false
if (!sl.validate(stamp)){
    //升级为悲观锁
    stamp = sl.readLock();
    try{
        //。。。
    }finally {
        //释放悲观读锁
        sl.unlockRead(stamp);
    }

StampedLock提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都是被阻塞的。
乐观读和乐观锁是不一样的,乐观读这个操作是无锁的,乐观读认为读的时候不会有写的操作。

示例

import java.util.concurrent.locks.StampedLock;

/**
 * 悲观锁乐观锁
 **/
public class Point {
    private int x;
    private int y;
    final StampedLock sl = new StampedLock();

    //计算到原点的距离
    int distanceFromOrigin() throws  Exception{
        //乐观锁
        long stamp = sl.tryOptimisticRead();
        //读入局部变量,读的过程数据可能被修改
        int curX = x;
        int 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);
    }
}
  1. StampedLock支持三种模式: 写锁悲观读乐观读
  2. 允许多个线程同时获取乐观锁和悲观读锁。
  3. 只允许一个线程获取写锁,写锁悲观读锁互斥的。
  4. 使用StampedLock一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()
  5. StampedLock里的写锁悲观读锁加锁成功之后,都会返回一个stamp;然后解锁的时候,需要传入这个 stamp
  6. StampedLock不支持重入(ReadWriteLock支持)。
  7. StampedLock的悲观读锁、写锁都不支持条件变量。
  8. StampedLock支持锁的降级(通过tryConvertToReadLock()方法实现)和升级(通过 tryConvertToWriteLock()方法实现),但是建议你要慎重使用。

数据库中的乐观锁

  1. 在表中添加一个数值型版本号字段version。
  2. 每次更新表时都将version字段加1。
  3. 修改数据时使用主键和version作为修改条件,version一致则会修改成功
  4. 如果修改失败,通过主键查询获取最新数据再执行修改。
发布了249 篇原创文章 · 获赞 417 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_33709508/article/details/105468264