synchronized 和 AQS

  • Post author:
  • Post category:其他




synchronized
  • 三种加锁方式

    • 同步代码块
    • 修改普通方法
    • 修饰静态方法
  • 锁升级过程

    • 无锁

      • 当一个对象被创建之后,还没有线程进入,这个时候对象处于无锁状态
    • 偏向锁

      • 当锁处于无锁状态时,有一个 线程A 访问同步块并获取锁时,会在对象头和栈帧中的锁记录中记录 线程id,以后该线程在进入和退出同步块时不需要进行 CAS操作 来进行加锁和解锁,只需要简单的测试一下对象头中的 线程id 和当前线程是否一致
    • 轻量级锁

      • 在偏向锁的基础上,又有另外一个 线程B 进来,这时判断对象头中存储的 线程A 的 id 和 线程B 不一致,就会使用 CAS 竞争锁,并且升级为轻量级锁,会在线程栈中创建一个锁记录(Lock Record),将 Mark Word 复制到锁记录中,然后线程尝试使用 CAS 将对象头的 Mark Word 替换成指向锁记录的指针,如果成功,则当前线程获得锁;失败,表示其他线程竞争锁,当前线程便尝试 CAS 来获取锁
    • 重量级锁

      • 当线程没有获得轻量级锁时,线程会 CAS 自旋 来获取锁,当一个线程自旋10次之后,仍然未获得锁,那么就会升级成为重量级锁
      • 成为重量级锁之后,线程会进入阻塞队列(EntryList),线程不再自旋获取锁,而是由 CPU 进行调度,线程串行执行



AQS (AbstractQueuedSynchronizer)
  • 这个类在 java.util.concurrent.locks (j.u.c.locks) 包下面

  • AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock、ReentrantReadWriteLock 等等皆是基于 AQS 的

  • AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态;如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列(Craig Landin Hagersten、虚拟双向队列、FIFO 队列)锁实现的,即将暂时获取不到锁的线程加入到队列中

    • 为什么要用虚拟双向队列 ?

      请添加图片描述

      很明显嘛,假设队列是单向的如:head -> n1 -> n2 -> tail,出队的时候获取 n1 很简单,head.next 就行了,入队就麻烦了,因为是一个 FIFO 队列,后放的元素要放在尾部,要遍历整个链表到 n2,然后 n2.next = n3;n3.next = tail,入队的复杂度就是 O(n) ,而且 tail 也失去他的意义

    • 相反双向链表出队和入队都是 O(1) 时间复杂度,说白了空间换时间

  • AQS 定义两种资源共享方式

    • 独占锁和共享锁

      • 独占锁:同一个时刻只能有一个线程访问,无论是读锁还是写锁
      • 共享锁:同一个时刻只能有一个写锁,读锁可以有多个可以同一时刻访问
    • Exclusive:独占锁

      • 只有一个线程能执行,如 ReentrantLock,又可分为公平锁和非公平锁

        • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
        • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
    • Share:共享锁

      • 多个线程可同时执行,如 CountDownLatch、Semaphore、 CyclicBarrier、ReadWriteLock
  • AQS 模板方法

    • 使用者继承 AbstractQueuedSynchronizer 并重写指定的方法 (重写方法就是对于共享资源 state 的获取和释放)
    • 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法 (钩子方法)
    • AQS 使用模板方法模式,自定义同步器时需要重写下面几个 AQS 提供的模板方法:


      • isHeldExclusively() // 该线程是否正在独占资源,只有用到 condition才需要去实现它

      • tryAcquire(int) // 独占方式,尝试获取资源,成功则返回 true,失败则返回 false

      • tryRelease(int) // 独占方式,尝试释放资源,成功则返回 true,失败则返回 false

      • tryAcquireShared(int) // 共享方式,尝试获取资源,负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源

      • tryReleaseShared(int) // 共享方式,尝试释放资源,成功则返回 true,失败则返回 false



ReentrantLock 和 ReentrantReadWriteLock 的区别
  • ReentrantLock

    • 实现 Lock 接口
    • 独占锁
  • ReentrantReadWriteLock

    • 实现 ReadWriteLock 接口,有 readLock()、writeLock() 两个方法
    • 共享锁


公平锁 和 非公平锁 实现的区别
  • 公平锁:lock() 和 tryLock() 会直接调用 acquire() 方法
  • 非公平锁:lock() 和 tryLock() 会先调用 CAS 尝试获取锁资源,如果获取不到调用 acquire() 方法



版权声明:本文为qq_41956014原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。