本文核心点
- synchronized是非公平的锁!
- 有线程在执行,新进入的线程会进入这个cxq这个队列中!
- 本文释放锁分析使用的是默认策略(QMode=0):如果EntryList为空,则将cxq中的元素按原有顺序插入到EntryList,并唤醒第一个线程,也就是当EntryList为空时,是后来的线程先获取锁。_EntryList不为空,直接从_EntryList中唤醒线程。
synchronized到底是什么?
- synchronized是JVM内置锁,基于Monitor机制实现。
- 依赖底层操作系统的互斥原语Mutex(互斥量)。
- 表面上它是一个重量级锁,性能较低。
- 实际上JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、自适应自旋(Adaptive Spinning)等技术来减少锁操作的开销,内置锁的并发性能已经基本与Lock持平。
Monitor(管程/监视器)
- Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。
- 管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。
- synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。
管程模型
- 在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。
- 现在正在广泛使用的是MESA模型。
wait()、notify()和notifyAll()的使用
- 使用wait有个范式要求:while(条件不满足)
- 所有等待线程拥有相同的等待条件:使用notify()。
- 所有等待线程被唤醒后,执行相同的操作:使用notify()。
- 只需要唤醒一个线程:使用notify()。
- 其他时候尽量使用notifyAll()。
Java内置的管程:synchronized
- Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。
- MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。
Monitor机制在Java中的实现
- java.lang.Object 类定义了 wait(),notify(),notifyAll() 方法
- wait(),notify(),notifyAll()的具体实现,依赖于 ObjectMonitor(JVM内部的机制) 实现。
ObjectMonitor的主要数据结构
_header = NULL; //对象头 markOop
_count = 0;
_waiters = 0,
_recursions = 0; // synchronized是一个重入锁,这个变量记录锁的重入次数
_object = NULL; //存储锁对象
_owner = NULL; // 标识拥有该monitor的线程(当前获取锁的线程)
_WaitSet = NULL; // 调用wait阻塞的线程:等待线程组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; // 有线程在执行,新进入的线程会进入这个队列:多线程竞争锁会先存到这个单向链表中 (FILO栈结构:非公平!)
FreeNext = NULL ;
_EntryList = NULL ; //存放在进入或重新进入时被阻塞(blocked)的线程 (也是存竞争锁失败的线程)
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
synchronized的等待唤醒机制
- 在获取锁时,是将当前线程插入到cxq的头部。
- 在释放锁时默认策略(QMode=0):如果EntryList为空,则将cxq中的元素按原有顺序插入到EntryList,并唤醒第一个线程,也就是当EntryList为空时,是后来的线程先获取锁。_EntryList不为空,直接从_EntryList中唤醒线程。
synchronized下线程的执行流程:等待机制!
public class SyncQModeDemo {
public static void main(String[] args) throws InterruptedException {
SyncQModeDemo demo = new SyncQModeDemo();
demo.startThreadA();
// 控制线程执行时间
Thread.sleep(100);
demo.startThreadB();
Thread.sleep(100);
demo.startThreadC();
}
final Object lock = new Object();
public void startThreadA() {
new Thread(() -> {
synchronized (lock) {
log.debug("A get lock");
try {
lock.wait(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("A release lock");
}
}, "thread-A").start();
}
public void startThreadB() {
new Thread(() -> {
synchronized (lock) {
try {
log.debug("B get lock");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("B release lock");
}
}, "thread-B").start();
}
public void startThreadC() {
new Thread(() -> {
synchronized (lock) {
log.debug("C get lock");
}
}, "thread-C").start();
}
}
执行结果
- A get lock
- B get lock
- B release lock
- A release lock
- C get lock
为什么是这样的结果?
- 第一个线程正常执行:owner是第一个线程!
- 第二个线程进来,由于第一个在执行,他会阻塞:owner是第一个线程,cxq有第二个线程!
- 假设这时候线程一调用wait()方法:WaitSet有第一个线程,cxq有第二个线程!owner为空!
- 下一次进行争抢线程的使用权,EntryList是空的,cxq中的第线程去执行:WaitSet有第一个线程,owner是第二个线程!
- 这时候第三个线程进来:cxq有第三个线程,WaitSet有第一个线程,cxq有第三个线程!owner是第二个线程!
- 第二个线程执行完毕,唤醒其他线程,将WaitSet中的线程转移到EntryList:EntryList有第一个线程,cxq有第三个线程!
- 下一次进行争抢线程的使用权,EntryList有值,直接从EntryList里面唤醒线程:EntryList有第一个线程,owner是第一个线程!
- 第一个线程执行完毕,唤醒线程,只有cxq里面有线程,唤醒他:owner是第三个线程。
synchronized下线程的执行流程:竞争机制
public class SyncQModeDemo {
public static void main(String[] args) throws InterruptedException {
SyncQModeDemo demo = new SyncQModeDemo();
demo.startThreadA();
// 控制线程执行时间
Thread.sleep(100);
demo.startThreadB();
Thread.sleep(100);
demo.startThreadC();
}
final Object lock = new Object();
public void startThreadA() {
new Thread(() -> {
synchronized (lock) {
log.debug("A get lock");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("A release lock");
}
}, "thread-A").start();
}
public void startThreadB() {
new Thread(() -> {
synchronized (lock) {
try {
log.debug("B get lock");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("B release lock");
}
}, "thread-B").start();
}
public void startThreadC() {
new Thread(() -> {
synchronized (lock) {
log.debug("C get lock");
}
}, "thread-C").start();
}
}
执行结果
- A get lock
- A release lock
- C get lock
- B get lock
- B release lock
为什么是这样的结果?
- 第一个线程正常执行:owner是第一个线程!
- 第一个线程没执行完,第二个线程进来:owner是第一个线程!cxq中有第二个线程!
- 第一个线程没执行完,第三个线程进来:owner是第一个线程!cxq中有第二个线程,第三个线程(队列结构,第二个线程先进来,第三个线程后进来)!
- 第一个线程执行完,原顺序从cxq中转移线程到EntryList:EntryList中有第二个线程,第三个线程(队列结构,第二个线程先进来,第三个线程后进来)!
- 唤醒线程:owner是第三个线程!cxq中有第二个线程!
- 线程三执行完:唤醒第二个线程,执行!
结束语
- 获取更多有价值的文章,让我们一起成为架构师!
- 关注公众号,可以让你对MySQL有非常深入的了解
- 关注公众号,每天持续高效的了解并发编程!
- 这个公众号,无广告!!!每日更新!!!