并发编程(十二):ReadWriteLock

前面我们介绍了管程和信号量这两个同步原语在 Java 语言中的实现,理论上用这两个同步原语中任何一个都可以解决所有的并发问题。那 Java SDK 并发包里为什么还有很多其他的工具类呢?原因很简单:分场景优化性能,提升易用性

1.读写锁:ReadWriteLock

读写锁:

  1. 允许多个线程同时读共享变量
  2. 只允许一个线程写共享变量
  3. 如果一个线程正在执行写操作,此时限制读线程读共享变量

1.1.互斥锁与读写锁的重要区别以及适用场景

  1. 读写锁允许多个线程同时读共享变量,而互斥锁不允许
  2. 在读多写少的场景,读写锁的性能明显优于互斥锁

2.读写锁的注意点

2.1.关于锁升级和锁降级

锁升级:如果当前线程已经拿到了读锁,此时如果再去拿写锁(升级为写锁),这个过程称为锁升级,但是ReadWriteLock 不支持锁升级,此时会发生死锁,示例如下:

final ReadWriteLock rwl = new ReentrantReadWriteLock();
// 读锁  
final Lock readLock = rwl.readLock();
// 写锁
final Lock writeLock = rwl.writeLock();    

try {
      //先拿到读锁
      readLock.lock();
      //再拿到写锁,此时会发生锁升级以及死锁问题,类似mysql的读锁
      writeLock.lock();
      System.out.println("拿到写锁拉");
      System.out.println("锁降级拉");
      for (int i = 0; i < time; i++) {
        System.out.println("请求写锁:"+i+"秒");
        Thread.sleep(1000 * 1);
      }
    }finally {
      writeLock.unlock();
      readLock.unlock();
    }
  }

锁降级:如果当前线程拿到了写锁,此时如果再去上读锁(降级为读锁),这个过程称为锁降级,ReadWriteLock是支持的

其实读写锁的升级和降级或者说读写锁的上锁操作,其实也对应于MySQL的读锁和写锁:

xx.readLock()操作,其实就是相当于MySQL的读锁,只允许当前线程和其他线程上读锁,但是不允许上写锁,此时如果当前线程上了读锁又去上写锁(锁升级),就会出现死锁的场景

xx.writeLock操作,其实就是相当于MySQL的写锁,只允许当前线程(事务)上进行读写操作(上读锁和写锁),其余线程如果请求上写锁或读锁时,就会发送阻塞操作直至取得写锁的线程释放写锁。

总结一下:

  1. MySQL的读锁又上写锁,就是ReadWriteLock的锁升级的场景,但是ReadWriteLock不支持这个场景,并且会导致死锁

  2. MySQL的写锁的功能,就是ReadWriteLock的锁降级的过程

  3. ReadWriteLock的写锁已被申请之后,读锁只能等待写锁都被释放才能去上写锁,而读锁被申请后,其余线程还可以再次上读锁

  4. 读写锁中的每个线程只能释放自己线程申请的锁,否则释放锁的线程会抛出异常:

    image-20210816092050210

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×