---
synchronized、CAS、AQS
---

#### 目录

1. 思维导图
2. 概述
3. 常见术语
   - 悲观锁/乐观锁
   - 公平锁/非公平锁
   - 可重入锁/不可重入锁
   - 死锁
4. synchronized
   - 使用方式
   - 实现原理
   - 锁优化
5. CAS
6. AQS
   - Lock
   - ReadWriteLock
7. 参考

#### 思维导图

#### 概述

Java 中的并发锁大致上可以分为隐式锁和显式锁两种。隐式锁的代表就是 synchronized 关键字，显式锁主要包含两个接口：Lock 和 ReadWriteLock，主要实现类分别为 ReentrantLock 和 ReentrantReadWriteLock，这两个类都是基于 AQS（AbstractQueuedSynchronizer）队列同步器实现的，还有的地方把 CAS 也称为一种锁，在包括 AQS 在内的很多并发相关类中，CAS 都扮演了很重要的角色，比如 AtomicInteger 等原子操作类。

#### 常见术语

##### 悲观锁和乐观锁

悲观锁和独占锁是一个意思，它假设一定会发生冲突，因此获取到锁之后会阻塞其他等待线程。这么做的好处是简单安全，但是挂起线程和恢复线程都需要转入内核态进行，这样做会带来很大的性能开销。悲观锁的代表是 synchronized，然鹅在真实环境中，大部分时候都不会产生冲突。而乐观锁不一样，它假设不会产生冲突，先去尝试执行某项操作，失败了再进行其他处理（一般都是不断循环重试），这种锁不会阻塞其他线程，也不涉及上下文切换，性能开销小，代表实现是 CAS。

##### 公平锁和非公平锁

公平锁是指各个线程在获取锁前先检查有无排队的线程，按排队顺序去获取锁。非公平锁是指线程获取锁前不考虑排队问题，直接尝试获取锁。值得注意是，在 AQS 的实现中，一旦线程进入排队队列，即使是非公平锁，线程也得乖乖排队。

##### 可重入锁和不可重入锁

如果一个线程已经获取到了锁，那么它可以访问被这个锁锁住的所有代码块，不可重入锁与之相反。可重入锁包括 synchronized 和 ReentrantLock。

##### 死锁



### synchronized

悲观、可重入，已经不那么重量级的锁。

##### 使用方式

- 修饰静态方法，锁 class 对象
- 修饰非静态方法，锁当前实例对象
- 修饰代码块，锁的是代码块里的对象

每个对象都有一个锁和一个等待队列，锁只能被一个线程持有，其他需要获取锁的线程需要阻塞等待，锁释放之后，对象会从队列中取出一个线程并唤醒，唤醒哪个线程是不确定的，不保证公平性。

多个线程是可以同时执行同一个 synchronized 实例方法的，只要它们访问的对象是不同的。

synchronized 锁住的是对象而非代码，只要访问的是同一个对象的方法，都得顺序访问。

##### 实现原理

synchronized 是基于 Java 对象头和 Monitor 机制来实现的。

- Java 对象头

  一个对象在内存中包含三个部分：对象头、实例数据、对其填充。其中 Java 对象头包含两个部分：

  - Class Metadata Adderess 类型指针

    存储类的元数据的指针，虚拟机通过这个指针找到它是哪个类的实例。

  - Mark Work 标记字段

    存储对象自身的运行时数据，包括 hashCode、GC 分代年龄、锁状态标志等。

- Monitor

  Mark Word 有一个字段指向 monitor 对象，monitor 中记录了锁的持有线程，等待的线程队列等信息，前面说的每个对象都有一个锁和一个等待队列，就是在这实现的。

那 JVM 是如何把 synchronized 与 monitor 关联起来的呢？可以分为两种情况：

1. 同步代码块，编译时会直接在同步代码块前加上 monitorenter 指令，代码块后加上 monitorexit 指令。
2. 同步方法，虚拟机会为方法设置 ACC_SYNCHRONIZED 标志，在调用方法的时候根据这个标志判断是否是同步方法。

##### 锁优化

JDK 1.6 对锁的实现引入了大量的优化，包括自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁来减少锁操作的开销。

锁主要存在四种状态：依次是无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。锁可以升级但是不能降级，这种策略是为了提高获取锁和释放锁的效率。

- 重量级锁

  重量级锁就是采用互斥量 Mutex 来控制对互斥资源的访问，在 Java 中被抽象为监视器锁 Monitor，这种同步方式成本非常高，包括内核态到用户态切换，性能差。

- 自旋锁与自适应自旋

  在许多应用中，锁定状态只会持续很短的时间，为了这么一点时间就去挂起、恢复线程，不值得。我们可以让等待线程执行一定次数的循环，在循环中去获取锁，这项技术称为自旋锁，它可以节省系统切换线程的消耗，但仍然占有处理器。在之后又引入自适应的自旋锁，不再通过次数来限制，而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

- 锁消除

  虚拟机在运行时，如果发现一段被锁住的代码中不可能存在共享数据，就会将这个锁清除。

- 锁粗化

  当虚拟机检测到有一串零碎的操作都对同一个对象加锁时，会把锁扩展到整个操作序列外部。如 StringBuffer 的 append 操作。

- 轻量级锁

  对绝大部分锁来说，整个同步周期内不存在竞争，如果没有竞争，轻量级锁可以使用 CAS 操作来避免使用互斥量的开销。

- 偏向锁

  偏向锁的核心思想是，如果一个线程获得了锁，那么锁就进入偏向模式，当这个线程再次请求锁时，无需再做任何同步操作，即可获取锁。

#### CAS

CAS 即 CompareAndSwap 比较并替换。CAS 机制当中使用了三个基本操作数：内存地址 V，旧的预期值 A，要修改的新值 B。更新一个变量的时候，只有当变量的预期值 A 和内存地址 V 和实际值相同时，才会将内存地址 V 对应的值修改为 B。

但是问题也是显而易见的：

1. ABA 问题

   加一个版本号。

2. 不能保证代码块的原子性

3. 在多次尝试更新一个值时不成功，CPU 开销大

在 Java 并发包中 AtomicInteger、等都是它的实现。

#### AQS



#### 参考

[从 synchronized 到 CAS 和 AQS - 彻底弄懂 Java 各种并发锁](https://juejin.im/post/5c37377351882525ec200f9e)

[synchronize早已经没那么笨重](https://juejin.im/post/5bff854b5188250e8601ec90)

[什么是 CAS 机制？](https://www.jianshu.com/p/41216f83c0e1)