目录
[一. 悲观锁和乐观锁](#一. 悲观锁和乐观锁)
[二. 重量级锁和轻量级锁](#二. 重量级锁和轻量级锁)
[三. 挂起等待锁和自旋锁](#三. 挂起等待锁和自旋锁)
[四. 公平锁和非公平锁](#四. 公平锁和非公平锁)
[五. 可重入锁和不可重入锁](#五. 可重入锁和不可重入锁)
[六. 读写锁](#六. 读写锁)
[七. synchronized](#七. synchronized)
[八. 锁消除](#八. 锁消除)
[九. 锁粗化](#九. 锁粗化)
一. 悲观锁和乐观锁
1. 乐观锁: 乐观锁 在加锁时, 假设出现锁冲突的概率不大 --> 接下来围绕加锁做的工作就更少.
2. 悲观锁: 悲观锁 在加锁时, 假设出现锁冲突的概率很大 --> 接下来围绕加锁做的工作就更多.
注\]: synchronized这个锁是"自适应锁". 它***在初始情况下是乐观的, 预估出现锁冲突的概率不大*** . 但是会统计锁冲突的次数, 当***所冲突的次数达到一定值之后, 就会从乐观锁转变为悲观锁***. ### 二. 重量级锁和轻量级锁 **1. 重量级锁:** **加锁的开销比较大**, 围绕加锁要做的工作更多. (一般来说, *悲观锁都是重量级的*). **2. 轻量级锁:** **加锁的开销比较小**, 围绕加锁要做的工作更少. (一般来说, *乐观锁都是轻量级的*). ### 三. 挂起等待锁和自旋锁 **1. 挂起等待锁:** 挂起等待锁 就是**悲观锁(重量级锁)的一种典型实现.** 挂起等待锁采用的等待策略是 "挂起等待", 等待过程中释放CPU资源, 这样的话就**不用一直占用CPU等待锁资源, 可以让出CPU资源做别的事情了.** 如果锁资源释放, 会以某种方式通知该线程. **2. 自旋锁:** 自旋锁 就是**乐观锁(轻量级锁)的一种典型实现.** 自旋锁采用的等待策略是 "忙等", 等待过程中不会释放CPU资源, 等待过程中**一直不停地检测锁是否被释放, 如果释放, 就有机会立即获取锁资源.** ### 四. 公平锁和非公平锁 **1. 公平锁:** 公平锁**确保** **锁的获取是按照线程请求的顺序进行的** . (即: ++最先请求的线程会最先获取到锁++). **2. 非公平锁:** 非公平锁**不能保证** **锁的获取是按照线程锁请求的顺序进行的** . (++当锁被释放时, 任何线程都有机会获得锁++, 即使是刚开始尝试获取锁的线程). \[注\]: synchronized就属于非公平锁, 当synchronized的锁资源释放后, 等待锁资源的n个线程就会重新竞争, 下一个是哪个线程拿到锁是不确定的. ### 五. 可重入锁和不可重入锁 **1. 可重入锁:** 可重入锁是指**同一个线程可以多次获取同一把锁** . 如果某线程已持有锁, 那么**当该线程再次尝试获取该锁资源时** (再次进入由这个锁保护的代码块) , **不会发生阻塞**. (可重入锁可以防止死锁的发生,因为同一个线程可以重复获取已经持有的锁) **2. 不可重入锁:** 不可重入锁是指**如果某线程已经持有锁, 那么它不能再次获取这个锁** , *++否则就会导致死锁.++* java中也对可重入锁进行了封装. java中用 ReentrantLock 这个类来实现可重入锁. **3. ReentrantLock 和 synchronized的区别** (1) synchronized 是关键字, 而ReentrantLock 是java标准库中的一个类. (2) synchronized 通过代码块加锁解锁, 而ReentrantLock通过 lock() 和 unlock() 实现加锁解锁. (3) synchronized 没有 try-lock 这样的锁风格, 当加锁失败的时候, 就会阻塞等待, 等待锁资源释放; 而ReentrantLock提供了 try-lock 这样的锁风格, 当加锁失败的时候, 不会阻塞等待, 而是直接返回一个返回值, 通过返回值来表示加锁成功还是失败. (3) synchronized 一定是非公平锁, 无法修改; 而 ReentrantLock 默认为非公平锁, 但是可以通过给构造方法传入参数来把 ReentrantLock 设定成公平锁. (4) synchronized 的等待和唤醒都是通过wait() -- notify() 来完成的; 而ReentrantLock提供了功能更强的"等待--通知"机制, 基于Condition类实现. ### 六. 读写锁 读写锁是一种用于**解决多线程环境中*读操作和写操作之间冲突*** 的锁机制. 读写锁可以提高并发程序的性能, 尤其是在++*读操作远多于写操作的场景*++中. #### 1. 分类: **(1) 读锁:** 多个线程可以同时持有读锁. **(2) 写锁:** 写锁是"排他"的, 即任何时候只能有一个线程持有写锁, 并且在此期间不能有其他线程进行读操作或者写操作. #### 2. 特点: **(1) 读读共享: 多个线程可以同时获取读锁**. **(2) 读写互斥: 读操作和写操作不能同时进行**. (如果某线程持有写锁, 那么其他线程无法获取读锁或者写锁) **(3) 写写互斥: 写操作不能同时进行**. (如果某线程持有写锁, 那么其他线程不能获取写锁) #### 3. 实现方式 java中, **ReadWriteLock 接口** 和++它的实现类++ **ReentrantReadWriteLock** 提供了++*读写锁的功能*++. ```java import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class Demo33 { public class ReadWriteLockExample { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //创建一个读写锁对象 private int data = 0; // 共享数据 // 读操作 public int read() { readWriteLock.readLock().lock(); //调用读锁 try { // 执行读操作 return data; } finally { readWriteLock.readLock().unlock(); } } // 写操作 public void write(int value) { readWriteLock.writeLock().lock(); //调用写锁 try { // 执行写操作 data = value; } finally { readWriteLock.writeLock().unlock(); } } } } ``` 上述代码中, **read操作获取读锁, 允许多个线程同时获取数据** ; **write操作获取写锁, 确保在++写入或修改数据时++ 没有其他线程进行读写操作**. ### 七. synchronized #### 1. synchronized锁的特点 (**1) 悲观乐观 --\> 自适应** **(2) 重量轻量 --\> 自适应** **(3) 挂起等待 / 自旋 --\> 自适应** **(4) 是非公平锁** **(5) 是可重入锁** **(6) 不是读写锁** #### 2. synchronized的加锁过程 synchronized的加锁过程, 实际上是一个**"锁升级"**的过程. 使用synchronized加锁, 刚开始, synchronized会处于"偏向锁"的状态, 即只是 "做个标记" , 但不会真正加锁. 然后, 如果出现锁竞争的话, "偏向锁" 会升级到 "轻量级锁", 之后, 线程进一步统计锁竞争的次数和频率, 当达到一定程度的时候, "轻量级锁" 就会升级到 "重量级锁". (synchronized加锁过程: **无锁 --\> 偏向锁 --\> 轻量级锁 --\> 重量级锁**) \[注\]: 偏向锁, 并不会真的加锁, 而只是"做一个标记", ++*标记的过程, 开销很小, 非常的轻量和高效*++. ### 八. 锁消除 "锁消除"机制 是编译器的自动优化策略. 比如我们写了一个**带synchronized锁的代码** , 编译器就会对我们加锁的代码做出判定, 如果**判断得到这里没有必要加锁, 就会自动把这里的锁消除掉**. ### 九. 锁粗化 在说锁粗化之前, 我们首先得明确一个概念: "锁的粒度". --\> 一个锁**保护的代码范围越广** , 那么这个**锁的粒度就越粗** ; 一个锁**保护的代码范围越小** , 那么这个**锁的粒度就越细** . 那么锁的粗化, 就是把多个"细粒度"的锁, 合成粗粒度的锁. 当编译器检测到*++一系列连续的操作都在对同一个锁进行加锁和解锁时, 会将这些操作**合并为一个更大的锁区域**, 从而减少锁的开销.++* 锁粗化的优点: 减少了对锁的获取和释放次数, 降低了上下文切换和调度的开销, 减少了线程因频繁获取和释放锁而产生的竞争, 提高了程序的吞吐量。