ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2022-01-17 01:31:45  阅读:222  来源: 互联网

标签:cxq Monitor synchronized lock 管程 Thread EntryList 线程


本文核心点

  • 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模型。

MESA模型.png

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

  • 使用wait有个范式要求:while(条件不满足) {  wait(); }
  • 所有等待线程拥有相同的等待条件:使用notify()。
  • 所有等待线程被唤醒后,执行相同的操作:使用notify()。
  • 只需要唤醒一个线程:使用notify()。
  • 其他时候尽量使用notifyAll()。

Java内置的管程:synchronized

Java内置的管程:synchronized.png

  • 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的等待唤醒机制

synchronized的等待唤醒机制.png

  • 在获取锁时,是将当前线程插入到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有非常深入的了解
  • 关注公众号,每天持续高效的了解并发编程!
  • 这个公众号,无广告!!!每日更新!!!
    作者公众号.jpg

标签:cxq,Monitor,synchronized,lock,管程,Thread,EntryList,线程
来源: https://www.cnblogs.com/zfcq/p/15811936.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有