乐趣区

关于java:看完这篇Lock和AQS就是弟弟

大家好,这里是 淇妙小屋 ,一个分享技术,分享生存的博主
以下是我的主页,各个主页同步更新优质博客,创作不易,还请大家点波关注
掘金主页
知乎主页
Segmentfault 主页
开源中国主页
后续会公布更多 MySQL,Redis,并发,JVM,分布式等面试热点常识,以及 Java 学习路线,面试重点,职业规划,面经等相干博客
转载请表明出处!

0. Lock 与 AQS 类图

1. Lock 接口

1.1 Lock 的定义

Lock 接口定义了锁的 API 操作,用于实现 java 中锁机制

public interface Lock {

    // 如果锁可用,则取得锁后返回
    // 如果锁不可用,那么以后线程会阻塞,直到获取锁后才会返回
    void lock();
    
    // 可中断的获取锁(取得锁的过程可中断)
    // 如果锁可用,则取得锁后返回
    // 如果锁不可用,那么线程会阻塞获取锁,阻塞获取锁的过程是可中断的,受到中断会抛出 InterruptedException
    void lockInterruptibly() throws InterruptedException;
    
    // 非阻塞的尝试取得锁
    // 如果锁可用,取得锁后,返回 true
    // 如果锁不可用,返回 false
    boolean tryLock();

    // 尝试获取锁(取得锁的过程可中断)
    // 如果锁可用,取得锁后,返回 true
    // 如果锁不可用,那么在指定工夫内会一直的尝试取得锁
    // 如果胜利取得锁——返回 true
    // 如果被中断——抛出 InterruptedException
    // 如果超时——返回 false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 开释锁
    void unlock();

    // 获取 Condition 对象,Condition 对象与以后 Lock 对象绑定,以后线程只有取得了锁
    // 能力调用该 Condition 对象的 await()办法,而调用后,以后线程将开释锁,进入 Conditon 对象的期待队列中 WATING
    // 当其余线程调用 Condition 对象的 signal(),才会唤醒 Condition 对象的期待队列中的 WATING 线程
    Condition newCondition();}

1.2 Lock 与 sychronized 的异同

  • 共同点

    • Lock 的子类中有可重入锁
  • 区别

    • Lock 显式的获取锁与开释锁

      sychronized 隐式的获取锁与开释锁

    • Lock 只能给代码块上锁

      sychronized 能够给代码块,办法上锁

    • Lock 依赖 JDK 实现

      sychronized 依赖 JVM 实现

    • Lock 有独占模式与共享模式,每个模式还分偏心锁与非偏心锁

      sychronized 是独占模式的非偏心锁

    • Lock 是可中断的,线程在取得锁的过程中是能够影响中断

      sychronized 不可中断,线程在阻塞期待锁的开释的时候,是不会响应中断的

    • Lock 能够设定超时工夫,超时会返回

      sychronized 不行

2. Condition 接口

2.1 Condition 接口定义

//Condition 对象——1. 相当于一个期待队列  2. 必须与一个锁实例绑定  3. 一个锁实例能够有多个 Condition 对象
// 相当于 monitor 对象的 WaitSet
// 必须持有 Condition 对象绑定的锁,才能够调用其办法
public interface Condition {// 相当于 Object.wait(),可中断
    // 持有锁的线程开释锁,进入期待队列中,线程状态更变为 WATING,直到以下 2 种状况醒来
    //1. 有其余线程调用期待队列的 signal()或 signalAll()
    //2. 被其余线程中断
    // 醒来后的线程会从期待队列挪动到锁实例的同步队列,状态由 WAITING 更改为 BLOKING
    void await() throws InterruptedException;

    // 同 await(),然而不可中断
    void awaitUninterruptibly();


    // 同 await(), 然而多了一种唤醒状况——期待超时(单位纳秒)long awaitNanos(long nanosTimeout) throws InterruptedException;

    // 同 await(), 然而多了一种唤醒状况——期待超时
    boolean await(long time, TimeUnit unit) throws InterruptedException;

   
    // 同 await(), 然而多了一种唤醒状况——期待超时
    boolean awaitUntil(Date deadline) throws InterruptedException;

    // 将期待队列中的首节点挪动到锁实例的同步队列,而后通过 LockSupport 唤醒线程
    void signal();

    // 将期待队列中的全副节点挪动到锁实例的同步队列,而后通过 LockSupport 唤醒线程
    void signalAll();}

2.2 Condition 与 monitor 的区别

  • Condition——期待队列中期待的线程是可响应中断的

    monitor——期待队列中期待的线程不可响应中断

  • Condition————期待队列中的线程能够指定期待到将来的某个具体工夫点

    monitor——不反对

3. AQS

3.1 AQS 介绍

AbstractQueuedSynchronizer 形象队列同步器——用于构建锁或其余同步组件的根底框架

子类通过继承 AQS 并实现它的形象办法来实现锁

AQS 反对两种模式——独占模式,共享模式

3.2 AQS 构造

AOS 的外围字段——exclusiveOwnerThread

  • 独占模式下,持有锁的线程

AQS 最外围字段——state

  • state用于示意同步器的状态 (可称为 同步状态)
  • AQS 的不同子类对 state 的使用不同

    • 对于 ReentrantLock

      • state=0——示意同步器没有被占用(没有线程持有锁)

        state!=0——示意同步器已被占用(有线程正在应用锁)

      • 尝试获取锁——查看 state 的值是否为 0,如果为 0 尝试用 CAS 批改其值,如果不为 0—则未获得锁

        尝试开释锁——将 state 的值 CAS 批改为 0

    • 对于 CountDownLatch,CyclicBarrier

      • state 有不同的使用

Node 的状态——waitStatus

  • CANCELLED(1):同步队列中的节点被中断或者超时
  • INITIAL(0):初始化状态
  • SIGNAL(-1)
  • CONDITION(-2):示意节点在期待队列中
  • PROPAGATE(-3):

AQS 有 5 个办法供子类锁去实现

  • 独占模式

    • boolean tryAcquire()
    • boolean tryRelease()
    • boolean IsHeldExclusively()
  • 共享模式

    • boolean tryAcquireShared()
    • boolean tryReleaseShared()

3.3 AQS 独占模式

AQS 独占模式下

  • 同步队列的首节点持有锁
  • 如果线程尝试获取锁失败,那么会封装成一个 Node,而后退出同步队列,并开始自旋
  • 节点从同步队列移除的条件——前继节点为首节点 and 胜利取得锁
  • 节点开释锁时——会唤醒其后继节点

3.3.1 获取锁

同步队列中,只有当节点的前继节点是首节点,能力尝试获得锁,起因如下

  1. 首节点是获得锁的节点,首节点开释锁后,会唤醒其后继节点

    后继节点被唤醒后须要查看本人的前继节点是否为首节点

  2. 保护同步队列的 FIFO 准则

3.3.2 开释锁

3.3.3 超时获取锁

过程根本与 获取锁 雷同

区别在于

  1. 每次自旋操作,在判断本人是否须要被阻塞之前,会优先判断是否已超时,如果超时了,就返回 false
  2. 如果须要被阻塞,还会查看残余的工夫有没有大于阈值

    如果大于阈值——通过 LockSupport 让线程进行超时阻塞

    LockSupport.parkNanos(this, nanosTimeout);

3.4 AQS 共享模式

3.4.1 共享模式与独占模式的区别

共享模式与独占模式的区别在于——同一时刻是否有多个线程能够同时获取到锁

  • 共享模式拜访资源——其余共享式拜访容许,独占式拜访不容许
  • 独占模式拜访资源——其余拜访一律不容许,被阻塞

3.4.2 共享模式取得锁

3.4.3 共享模式开释锁

4. ReentrantLock 实现 AQS 的独占模式

4.1 ReentrantLock 介绍

可重入:任意线程取得锁后可能再次获取该锁而不会被锁阻塞

ReentrantLock 实现了 AQS 的独占模式,是一个可重入锁,还分为 偏心锁 非偏心锁

  • 偏心锁:先对锁进行获取申请的线程肯定先取得锁
  • 非偏心锁

非偏心锁 的效率高于 偏心锁

非偏心锁 可能呈现 线程饥饿问题——局部线程迟迟无奈取得资源

ReentrantLock大多数办法的实现都是 Sync 及其子类来实现,ReentrantLock 只是对外裸露了接口

4.2 ReentrantLock 取得锁

4.2.1 非偏心锁

4.2.2 偏心锁

4.2.3 偏心锁与非偏心锁的不同

FairSync 和 NonfairSync 的 lock() 和 tryAcquire() 逻辑不同

  1. 非偏心锁在 lock()办法的开始就会尝试去通过 CAS 批改同步状态以取得锁,偏心锁不会
  2. 在自旋时,非偏心锁和偏心锁都会在 前继节点为同步队列首节点时 调用 tryAcquire()尝试获取锁

    tryAcquire()中,如果 state 为 0,那么非偏心锁不会关怀节点在同步队列中的地位,间接尝试 CAS 批改 state 取得锁;然而非偏心锁关怀节点的地位,会查看是否有前继节点,如果有,就会放弃

上述 2 点 保障了偏心锁肯定是——先对锁进行获取申请的线程肯定先取得锁,而非偏心锁不肯定

4.3 ReentrantLock 开释锁

偏心锁开释锁与非偏心锁开释锁采纳同一个逻辑

退出移动版