第三章 说说 Java "锁"事
3.1 从轻松的乐观锁和悲观锁
-
悲观锁: 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改,synchronized 和 Lock 的实现类都是悲观锁,适合写操作多的场景,先加锁可以保证写操作时数据正确,显示的锁定之后再操作同步资源-----狼性锁
-
乐观锁: 认为自己在使用数据的时候不会有别的线程修改数据或资源,不会添加锁,Java中使用无锁编程来实现,只是在更新的时候去判断,之前有没有别的线程更新了这个数据,如果这个数据没有被更新,当前线程将自己修改的数据成功写入,如果已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如:放弃修改、重试抢锁等等。判断规则有:版本号机制 Version,最常采用的是 CAS 算法,Java 原子类中的递增操作就通过CAS自旋实现的。-----适合读操作多的场景,不加锁的特性能够使其读操作的性能大幅提升,乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命---佛系锁
3.2 通过 8 种情况演示锁运行案例,看看锁到底是什么
3.2.1 锁相关的 8 种案例演示 code
public class Lock8Demo {
/**
* 现象描述:
* 1 标准访问ab两个线程,请问先打印邮件还是短信? --------先邮件,后短信 共用一个对象锁
* 2. sendEmail钟加入暂停3秒钟,请问先打印邮件还是短信?---------先邮件,后短信 共用一个对象锁
* 3. 添加一个普通的hello方法,请问先打印普通方法还是邮件? --------先hello,再邮件
* 4. 有两部手机,请问先打印邮件还是短信? ----先短信后邮件 资源没有争抢,不是同一个对象锁
* 5. 有两个静态同步方法,一步手机, 请问先打印邮件还是短信?---------先邮件后短信 共用一个类锁
* 6. 有两个静态同步方法,两部手机, 请问先打印邮件还是短信? ----------先邮件后短信 共用一个类锁
* 7. 有一个静态同步方法 一个普通同步方法,请问先打印邮件还是短信? ---------先短信后邮件 一个用类锁一个用对象锁
* 8. 有一个静态同步方法,一个普通同步方法,两部手机,请问先打印邮件还是短信? -------先短信后邮件 一个类锁一个对象锁
*/
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "a").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
// phone.sendSMS();
phone2.sendSMS();
// phone.hello();
}, "b").start();
}
}
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendEmail");
}
public static synchronized void sendSMS() {
System.out.println("sendSMS");
}
public void hello() {
System.out.println("hello");
}
}
结论:
-
对于普通同步方法,锁是当前实例对象,通知指 this,所有同步方法用的都是同一把锁 --> 实例对象本身(即类)
-
对于静态同步方法,锁的是当前类的 Class 对象
-
对于同步方法块,锁的是 synchronized 括号内的对象
3.2.2 synchronized 有三种应用方式
-
作用于实体方法,当前实例加锁,进入同步代码块前要获得当前实例的锁
-
作用于代码块,对括号里配置的对象加锁
-
作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁
3.2.3 从字节码角度分析synchronized实现
-
synchronized 同步代码块
javap -c (附加信息) ***.class 文件反编译实现使用的是
monitorenter和monitorexit指令![image]()
-
synchronized 普通同步方法
javap -v (附加信息) ***.class 文件反编译调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程会将现持有 monitor 锁,然后再执行该方法,最后在方法完成(无论是否正常结束)时释放 monitor
![image]()
-
synchronized 静态同步方法
ACC_STATIC、ACC_SYNCHRONIZED 访问标志区分该方法是否是静态同步方法
![image]()
3.2.4 反编译 synchronized 锁的是什么
为什么任何一个对象都可以成为一个锁?
C++ 源码:ObjectMonitor.java -> ObjectMonitor.cpp -> ObjectMonitor.hpp
每个对象天生都带着一个对象监视器,每一个被锁住的对象都会和 Monitor 关联起来
总结:指针指向 Monitor 对象(也称为管程或监视器)的真实地址。每个对象都存在着一个 monitor 与之关联,当一个
monitor 被某个线程持有后,它便处于锁定状态。在 java 虚拟机(HotSpot)中,monitor 是由 OnjectMonitor 实现的,七主要的数据结构如下(位于 HotSpot 虚拟机源码 ObjectMonitor.hpp 文件,C++ 实现)


3.2.5 对于 Synchronized 关键字
后面章节详说
3.3 公平锁和非公平锁
3.3.1 何为公平锁/非公平锁
-
公平锁:是指多个线程按照申请锁的顺序来获取锁,这里类似于排队买票,先来的人先买,后来的人再队尾排着,这是公平的
//表示公平锁,先来先得 Lock lock = new ReentrantLock(true) -
非公平锁:是指多个线程获取锁的顺序并不是按照申请的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级反转或者饥饿的状态(某个线程一直得不到锁)
//表示非公平锁,后来的也可能先获得锁,默认为非公平锁 Lock lock = new ReentrantLock(false)
面试题:为什么会有公平锁/非公平锁的设计?为什么默认非公平?
-
恢复挂起的线程到真正的锁获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从 CPU 的角度来看,这个时间差存在的还是很明显的。所以非公平锁能充分地利用 CPU 的时间片,尽量减少 CPU 空闲状态的时间
-
使用多线程很重要的考量点是线程切换的开销,采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得很大,所以就减少了线程开销
什么时候用公平?什么时候用非公平?
如果是为了更高的吞吐量,很显然非公平锁时比较合适的,因为节约了很多线程切换的时间,吞吐量自然就上去了;否则就用公平锁,大家公平使用
3.3.2 预埋伏 AQS
后续深入分析
3.4 可重入锁(递归锁)
3.4.1 概念说明
是指在同一线程在外层方法获取到锁的时候,在进入该线程的内层方法会自动获取锁(前提,锁对象是同一个对象),不会因为之前已经获取过还没有释放而阻塞
优点之一就是可以一定程度上避免死锁
3.4.2 可重入锁种类
-
隐式锁(即 synchronized 关键字使用的锁),默认是可重入锁
在一个 synchronized 修饰的方法或者代码块的内部调用本类的其他 synchronized 修饰的方法或者代码块时,是永远可以得到锁
public class ReEntryLockDemo { public synchronized void m1() { System.out.println(Thread.currentThread().getName() + "\t come in"); m2(); System.out.println(Thread.currentThread().getName() + "\t come out"); } public synchronized void m2() { System.out.println(Thread.currentThread().getName() + "\t come in"); m3(); } public synchronized void m3() { System.out.println(Thread.currentThread().getName() + "\t come in"); } /** * 运行结果: * 外层锁获得 * 中层锁获得 * 内层锁获得 */ public static void main(String[] args) { ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo(); Thread t1 = new Thread(() -> { reEntryLockDemo.m1(); }, "t1"); t1.start(); } /** * 运行结果: * t1 come in * t1 come in * t1 come in * t1 come out */ private static void reEntryM1() { final Object obj = new Object(); new Thread(() -> { synchronized (obj) { System.out.println("外层锁获得"); synchronized (obj) { System.out.println("中层锁获得"); synchronized (obj) { System.out.println("内层锁获得"); } } } }, "t1").start(); }
}
- 显示锁(即 Lock)也有 `ReentrantLock` 这样的可重入锁
```java
public class ReEntryLockDemo {
private static ReentrantLock lock = new ReentrantLock();
/**
* 运行结果:
* 外层锁获得
* 中层锁获得
* 内层锁获得
*/
public static void main(String[] args) {
try {
lock.lock();
System.out.println("外层锁获得");
try {
lock.lock();
System.out.println("中层锁获得");
try {
lock.lock();
System.out.println("内层锁获得");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
}
3.5 死锁及排查
3.5.1 概念
死锁是指两个或两个以上的线程在执行的过程中,因抢夺资源而造成的一种互相等待的现象,若无外力干涉,则它们无法再继续推进下去
产生原因:
-
系统资源不足
-
程序运行顺序不合适
-
系统资源分配不当

3.5.2 写一个死锁代码
public class DeadLockDemo {
/**
* 运行结果:
* A线程获得锁A,希望获得锁B
* B线程获得锁B,希望获得锁A
*
* 出现死锁
*/
public static void main(String[] args) {
Object objectA = new Object();
Object objectB = new Object();
new Thread(() -> {
synchronized (objectA) {
System.out.println(Thread.currentThread().getName() + "线程获得锁A,希望获得锁B");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectB) {
System.out.println(Thread.currentThread().getName() + "线程获得锁B");
}
}
}, "A").start();
new Thread(() -> {
synchronized (objectB) {
System.out.println(Thread.currentThread().getName() + "线程获得锁B,希望获得锁A");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectA) {
System.out.println(Thread.currentThread().getName() + "线程获得锁A");
}
}
}, "B").start();
}
}
3.5.3 如何排查死锁
-
纯命令
-
jps l![image]()
-
jstack进程编号![image]()
-
-
图形化
jconsole![image]()
3.6 写锁(独占锁)/读锁(共享锁)
深度源码分析见后面
3.7 自旋锁 spinLock
深度源码分析见后面
3.8 无锁->独占锁->读写锁->邮戳锁
深度源码分析见后面
3.9 无锁->偏向锁->轻量锁->重量锁
深度源码分析见后面







浙公网安备 33010602011771号