1、类继承关系

Java线程知识总结(四):ReentrantLock & ReentrantReadWriteLock
接口:Lock,ReadWriteLock
实现:ReentrantLock,ReentrantReadWriteLock
Lock API:JDK提供,在J.U.C的包中

  • Lock接口简介
    Java线程知识总结(四):ReentrantLock & ReentrantReadWriteLock
    lock方法可以通过自旋tryLock进行实现

2、ReentrantLock

ReentrantLock:可重入锁

2.1、ReentrantLock数据结构与算法

Java线程知识总结(四):ReentrantLock & ReentrantReadWriteLock

  • 数据结构
    owner:相当于synchronized的object monitor中的owner,用于存储当前抢到锁的线程
    count:计数器,用于实现可重入功能
    waiter:相当于synchronized的object monitor中的EntryList,抢不到锁的线程就会进入waiter,并且线程被挂起
  • lock算法
  1. 查看count是否为0,如果为null,则CAS(0, 1)修改count,CAS成功后,将owner设置为当前线程(设置owner不需要CAS)
  2. 如果count不为0,说明等待队列,判断owner是否为当前线程,count自增(count自增不需要CAS)
  3. 如果以上不满足,则说明抢不到锁,进入waiter,并且挂起当前线程
  • unlock算法
  1. 判断owner是否为当前线程,如果不是,直接throw IllegalMonitorStateException
  2. 如果owner是当前线程,count自减,如果count不为0,则锁还没有释放
  3. 如果count已经为0,则说明锁成功释放,并把owner置为null(可以用CAS),并唤醒waiter中的第一个线程

2.2、Condition

Condition对象可以通过ReentrankLock的newCondition创建,提供await()以及signa()方法,功能类似于wait/notify。
Condition.await()/signal() 与wait/notify的区别:
相同点:

  • wait/notify必须在synchronized中使用,wait挂起线程且释放锁
  • 通过lock.newCondition()获取Condition对象await()也会挂起线程且释放锁,会被放入ReentrantLock的“WaitSet”

不同点:

  • wait/notify,一个synchronized中只有调用一个
  • lock接口里面,一个lock可以有多个condition(即ReentrantLock可以多次调用newCondition(),创建多个Condition对象,每个Condition都是独立的,不会相互影响)

3、ReentrantLock与synchronized对比

  • 性能上差异:
    单线程的情况下synchronized比Lock性能要好 -> 偏向锁,锁消除
    上升到重量级锁的时候synchronized和Lock算法一样
  • synchronized:StringBuilder要使用synchronized,大多数情况下在单线程下使用
    • 优点
      • 使用简单,语义清晰,哪里需要点哪里
      • 由JVM提供,提供了多种优化方案(锁粗化,锁消除,偏向锁,轻量级锁)
      • 锁的释放由JVM来完成,不用人工干预,也降低了死锁的可能性
    • 缺点
      • 无法实现一些锁的高级功能。ex.公平锁,中断锁,读写锁,共享锁等
  • Lock
    • 优点
      • 所有synchronized的缺点
      • 可以实现更多功能,让synchronized缺点更多
    • 缺点
      • 需要手动解锁unlock,新手使用不当可能造成死锁

4、ReentrantReadWrite

Java线程知识总结(四):ReentrantLock & ReentrantReadWriteLock

4.1、读写锁算法

  • 写锁lock算法
  1. 判断readCount是否为0,如果不为0,则说明读锁被占用,线程进入waiter
  2. 如果readCount为0,则判断writeCount是否为0,如果不为1,则判断owner是否为当前线程,是的话,writeCount自增(写锁重入)
  3. 如果writeCount为0,则CAS(0, 1),成功说明抢占写锁,并修改owner为当前线程
  4. CAS不成功,说明没有成功抢锁,则进入waiter等待队列
  • 写锁unlock算法
  1. 判断owner是否为当前线程,如果是,则writeCount自减
  2. 判断writeCount是否为0,如果为0,则说明释放写锁成功,设置owner为null。再判断readCount是否为0,如果不为0,则说明锁降级,不唤醒waiter头节点线程;否则唤醒waiter的头节点线程
  3. 如果writeCOunt不为0,则说明写锁还没有被释放
  • 读锁lock算法:读锁抢占成功会唤醒waiter的头节点线程
  1. 判断writeCount是否为0,如果writeCount为0,则判断readCount是否为0,如果为0,则CAS(0,1),如果不为0,则readCount自增,且唤醒waiter的头节点线程
  2. 如果writeCount不为0,则判断owner是否为当前线程,如果为当前线程,则readCount自增,且唤醒waiter的头节点线程
  3. 如果writeCount不为0,且owner不为当前线程,则进入waiter并挂起当前线程
  • 读锁unlock算法
  1. readCount自减,判断readCount是否为0,如果为0,说明读锁释放成功,唤醒waiter头节点线程
  2. 如果readCount不为0,则说明仍有其他线程在占用读锁,不进行其他操作
  • 锁降级:(读写锁,没有锁升级,读取了读锁之后,不可以读取写锁)
  1. Thread1加写锁 -> 写锁
  2. Thread1加读锁 -> 写锁,读锁
  3. Thread1释放写锁 -> 读锁(由写锁降级为读锁,其他线程可以加读锁)

4.2、AQS 抽象队列同步器

Java线程知识总结(四):ReentrantLock & ReentrantReadWriteLock
AQS根据ReentrantReadWriteLock算法,使用模板方法进行了实现。子类可以通过复写AQS的各个方法,实现不同的功能。
(try的方法都由子类进行实现)

  • tryAcquire == 写锁tryLock
  • tryRelease == 写锁tryUnlock
  • tryAcquireShared == 读锁tryLock
  • tryReleaseShared == 读锁tryUnlock

ps. AQS通过一个state实现了上述的readCount和writeCount,用前16位实现了readCount,后16位实现writeCount。目的是保证原子性操作

通过AQS实现的并发工具类:

  • CountDownLatch
  • Semaphore计数信号量
  • CyclicBarrier:循环栅栏
  • FutureTask:用于获取线程的执行结果

相关文章:

  • 2021-05-19
  • 2021-11-24
  • 2022-12-23
  • 2022-02-22
  • 2021-05-14
  • 2021-08-18
  • 2022-01-01
猜你喜欢
  • 2021-10-18
  • 2021-11-17
  • 2021-12-24
  • 2021-06-06
  • 2021-08-28
  • 2021-08-21
相关资源
相似解决方案