【并发编程】synchronized底层原理:Monitor(管程监视器)

【并发编程】synchronized底层原理:Monitor(管程监视器),第1张

【并发编程】synchronized底层原理:Monitor(管程/监视器) 本文核心点

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(条件不满足) { 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下线程的执行流程:等待机制!

看一段代码的执行结果

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 lockB get lockB release lockA release lockC 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 lockA release lockC get lockB get lockB release lock 为什么是这样的结果?

第一个线程正常执行:owner是第一个线程!第一个线程没执行完,第二个线程进来:owner是第一个线程!cxq中有第二个线程!第一个线程没执行完,第三个线程进来:owner是第一个线程!cxq中有第二个线程,第三个线程(队列结构,第二个线程先进来,第三个线程后进来)!第一个线程执行完,原顺序从cxq中转移线程到EntryList:EntryList中有第二个线程,第三个线程(队列结构,第二个线程先进来,第三个线程后进来)!唤醒线程:owner是第三个线程!cxq中有第二个线程!线程三执行完:唤醒第二个线程,执行! 结束语

获取更多有价值的文章,让我们一起成为架构师!关注公众号,可以让你对MySQL有非常深入的了解关注公众号,每天持续高效的了解并发编程!这个公众号,无广告!!!每日更新!!!

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5708422.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存