Synchronized关键字原理!
Synchronized关键字原理!
月伴飞鱼
Synchronized
可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
两类锁:
对象锁:
- 方法锁(默认锁对象为this当前实例对象)和同步代码块锁(自己指定锁对象)。
类锁:
- 修饰静态的方法或指定锁为Class对象。
底层实现
修饰方法:
- 在字节码上给方法加了一个 flag:
ACC_SYNCHRONIZED
。
代码块:
- 通过 monitorenter 和monitorexit 两个指令进行控制的。
- 本质上是通过 monitor 来实现的。
在Java虚拟机(HotSpot)中,Monitor是由
ObjectMonitor
实现的,其主要数据结构如下:
1 | ObjectMonitor() { |
每个 Java 对象在 JVM 的对等对象的头中保存锁状态,指向 ObjectMonitor。
- ObjectMonitor 保存了当前持有锁的线程引用
- EntryList 中保存目前等待获取锁的线程
- WaitSet 保存 wait 的线程。
计数器count:
- 每当线程获得 monitor 锁,计数器 +1,当线程重入此锁时,计数器还会 +1。
- 当计数器不为 0 时
- 其它尝试获取 monitor 锁的线程将会被保存到EntryList中,并被阻塞。
- 当持有锁的线程释放了monitor 锁后,计数器 -1。
- 当计数器归位为 0 时,所有 EntryList 中的线程会尝试去获取锁
- 但只会有一个线程会成功,没有成功的线程仍旧保存在 EntryList 中。
加锁时,即遇到Synchronized关键字时
- 线程会先进入monitor的
_EntryList
队列阻塞等待。如果monitor的
_owner
为空,则从队列中移出并赋值与_owner
。如果在程序里调用了wait()方法,wait方法会释放monitor锁
将
_owner
赋值为null,并进入_WaitSet
队列阻塞等待。这时其他在
_EntryList
中的线程就可以获取锁了。当程序里其他线程调用了notify/notifyAll方法时
就会唤醒
_WaitSet
中的某个线程,这个线程就会再次尝试获取monitor锁。如果成功,则就会成为monitor的owner。
当程序里遇到Synchronized关键字的作用范围结束时,就会将monitor的owner设为null,退出。
Synchronized和Lock的区别:
Synchronized属于JVM层面
- Lock是API层面的东西,JUC提供的具体类。
Synchronized不需要用户手动释放锁
- 当代码执行完毕之后会自动让线程释放持有的锁,Lock需要去手动释放锁。
Synchronized是不可中断的
- 除非抛出异常或者程序正常退出,Lock可中断。
Synchronized是非公平锁
- Lock默认是非公平锁,但是可以通过构造函数传入boolean类型值更改是否为公平锁。
Synchronized要么唤醒所有线程,要么随机唤醒一个线程
- Lock可以使用condition实现分组唤醒需要唤醒的线程。
Synchronized只能同时被一个线程拥有
- 但是 Lock 锁没有这个限制,例如在读写锁中的读锁,是可以同时被多个线程持有的。
Synchronized 锁了类,那再锁实例还能锁住吗?冲突吗?
Synchronized
锁住类和锁住实例是两种不同的锁机制,它们互不干扰。类级锁和实例级锁控制的资源不同,锁住类不会影响锁住实例。
- 你可以同时锁住类和不同实例,且它们是独立的。
Synchronized为什么需要维护一个计数器?独占锁一个状态表示就行了吧,计数的目的是啥?
通过记录线程 ID(或线程标识)可以判断一个锁是否被某个线程持有。
- 但无法准确反映嵌套调用的深度,容易导致锁的过早释放问题。
维护计数器并不是为了简单判断锁的持有者,而是为了支持可重入锁的完整语义。
1 | synchronized(obj) { |