竹笋

首页 » 问答 » 问答 » 并发编程synchronized底层
TUhjnbcbe - 2023/9/6 22:22:00

本文核心点

synchronized是非公平的锁!

有线程在执行,新进入的线程会进入这个cxq这个队列中!

本文释放锁分析使用的是默认策略(QMode=0):如果EntryList为空,则将cxq中的元素按原有顺序插入到EntryList,并唤醒第一个线程,也就是当EntryList为空时,是后来的线程先获取锁。_EntryList不为空,直接从_EntryList中唤醒线程。

synchronized到底是什么?

synchronized是JVM内置锁,基于Monitor机制实现。

依赖底层操作系统的互斥原语Mutex(互斥量)。

表面上它是一个重量级锁,性能较低。

实际上JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(LockCoarsening)、锁消除(LockElimination)、轻量级锁(LightweightLocking)、偏向锁(BiasedLocking)、自适应自旋(AdaptiveSpinning)等技术来减少锁操作的开销,内置锁的并发性能已经基本与Lock持平。

Monitor(管程/监视器)

Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。

管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。

synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。

管程模型

在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。

现在正在广泛使用的是MESA模型。

wait()、notify()和notifyAll()的使用

使用wait有个范式要求:while(条件不满足){wait();}

所有等待线程拥有相同的等待条件:使用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下线程的执行流程:等待机制!

看一段代码的执行结果

publicclassSyncQModeDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{SyncQModeDemodemo=newSyncQModeDemo();demo.startThreadA();//控制线程执行时间Thread.sleep();demo.startThreadB();Thread.sleep();demo.startThreadC();}finalObjectlock=newObject();publicvoidstartThreadA(){newThread(()-{synchronized(lock){log.debug("Agetlock");try{lock.wait();}catch(InterruptedExceptione){e.printStackTrace();}log.debug("Areleaselock");}},"thread-A").start();}publicvoidstartThreadB(){newThread(()-{synchronized(lock){try{log.debug("Bgetlock");Thread.sleep();}catch(InterruptedExceptione){e.printStackTrace();}log.debug("Breleaselock");}},"thread-B").start();}publicvoidstartThreadC(){newThread(()-{synchronized(lock){log.debug("Cgetlock");}},"thread-C").start();}}

执行结果

Agetlock

Bgetlock

Breleaselock

Areleaselock

Cgetlock

为什么是这样的结果?

第一个线程正常执行: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下线程的执行流程:竞争机制

看一段代码的执行结果

publicclassSyncQModeDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{SyncQModeDemodemo=newSyncQModeDemo();demo.startThreadA();//控制线程执行时间Thread.sleep();demo.startThreadB();Thread.sleep();demo.startThreadC();}finalObjectlock=newObject();publicvoidstartThreadA(){newThread(()-{synchronized(lock){log.debug("Agetlock");try{Thread.sleep();}catch(InterruptedExceptione){e.printStackTrace();}log.debug("Areleaselock");}},"thread-A").start();}publicvoidstartThreadB(){newThread(()-{synchronized(lock){try{log.debug("Bgetlock");Thread.sleep();}catch(InterruptedExceptione){e.printStackTrace();}log.debug("Breleaselock");}},"thread-B").start();}publicvoidstartThreadC(){newThread(()-{synchronized(lock){log.debug("Cgetlock");}},"thread-C").start();}}

执行结果

Agetlock

Areleaselock

Cgetlock

Bgetlock

Breleaselock

为什么是这样的结果?

第一个线程正常执行:owner是第一个线程!

第一个线程没执行完,第二个线程进来:owner是第一个线程!cxq中有第二个线程!

第一个线程没执行完,第三个线程进来:owner是第一个线程!cxq中有第二个线程,第三个线程(队列结构,第二个线程先进来,第三个线程后进来)!

第一个线程执行完,原顺序从cxq中转移线程到EntryList:EntryList中有第二个线程,第三个线程(队列结构,第二个线程先进来,第三个线程后进来)!

唤醒线程:owner是第三个线程!cxq中有第二个线程!

线程三执行完:唤醒第二个线程,执行!

建议阅读

「并发编程」从一个程序入门synchronized

「并发编程」原子操作的基础:CAS

「并发编程」深入简出的带你精通java线程

并发编程硬件理解:CPU缓存架构与缓存一致性协议

并发编程的基础:深入了解JMM

1
查看完整版本: 并发编程synchronized底层