说说Java锁事
3、说说Java锁事
3.1 大厂面试题复盘
3.2 悲观锁
- 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
- synchronized关键字和Lock的实现类都是悲观锁。
3.3 乐观锁
- 乐观锁
- 认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。
- 在Java中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。
- 如果这个数据没有被更新,当前线程将自己修改的数据成功写入。
- 如果这个数据已经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。
- 判断规则
- 版本号机制Version。
- 最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。
3.4 伪代码说明
3.5 锁相关的8种案例演示code
1 | class Phone //资源类 |
3.6 解释说明-小总结
3.7 JDK源码(notify方法)说明举例
3.8 synchronized同步代码块的反编译
3.9 m1方法里面自己添加一个异常试试
3.10 synchronized普通同步方法的反编译
3.11 大厂面试题讲解
3.12 管程
3.13 objectMonitor.hpp
3.14 提前剧透,混个眼熟
synchronized必须作用于某个对象中,所以Java在对象的头文件存储了锁的相关信息。锁升级功能主要依赖于MarkWord中的锁标志位和释放偏向锁标志位,后续讲解锁升级时候我们再加深,目前为了承前启后的学习,对下图先混个眼熟即可,O(∩_∩)O。
3.15 从ReentrantLock卖票demo演示公平和非公平现象
1 | class Ticket //资源类,模拟3个售票员卖完50张票 |
3.16 何为公平锁/非公平锁?
3.17 为什么会有公平锁/非公平锁的设计?为什么默认非公平?
- 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片,尽量减少CPU空闲状态时间。
- 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
3.18 什么时候用公平?什么时候用非公平?
- 如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则那就用公平锁,大家公平使用。
3.19 预埋伏AQS
3.20 可重入锁(又名递归锁)的说明
- 可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
- 如果是1个有synchronized修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
3.21 同步块
1 | public class Main { |
3.22 同步方法
1 | public class Main { |
3.23 objectMonitor.hpp
3.24 显式锁(即Lock)也有ReentrantLock这样的可重入锁
1 | public class Main { |
3.25 请写一个死锁代码case
1 | public class DeadLockDemo { |
3.26 小总结(重要)
指针指向monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)。