通过上一篇我们知道通过内存屏障,在反编译的汇编语言中我们看到基于lock cmpxchg和lock addl 前置指令解决了synchronized和volatile的可见性和有序性问题。也在反汇编中看到了synchronized中的monitorenter和monitorexit,这是synchronized实现原子性的关键,并且monitorexit出现了两次,就是为了包装程序在正常退出和异常退出的情况下都执行执行唤醒操作。monitor可以叫监视器,也可以翻译为管程,并不是java特有的产物。并发过程中除了有线程原子性的问题,我们还需要解决线程间的互斥还得数据同步

在解决并发问题的历史过程中,最早是使用信号量处理,在juc包中Semaphore还有其实现支持,将在编发编程工具中分析。往后才是管程模型的提出,但是管程和信号量是等价的,即管程能实现的功能(或效果)信号量也能实现。在java中synchronized就是管程模型的实现,只是有c语言实现不方便查看,但是在JDK5之后juc(java.util.concurrent)包下面的API就是基于volatile(解决了可见性和有序性)+ CAS 模拟管程实现。实现的核心由AQS(AbstractQueuedSynchronizer)实现,CAS使用sun.misc.UnSafe的CAS相关API实现。所以当前先使用ReentrantLock和Condition模拟管程实现【先大概看看管程是什么东西】,实现线程安全的原子性,即操作的过程对外不可见,外面不能看见执行中的中间状态,那么管程的思想就是执行的过程封装起来,满足条件后再唤醒其他(线程)操作,也就解决了原子性。

/***  使用{@link ReentrantLock} 和 {@link Condition} 模拟管程模型;在synchronized中只能有一个条件队列,*  而当前可以创建多个Condition即可**  <p>*      对于入队操作,如果队列已满,就需要等待直到队列不满,所以这里用了notFull.await();。*      对于出队操作,如果队列为空,就需要等待直到队列不空,所以就用了notEmpty.await();。*      如果入队成功,那么队列就不空了,就需要通知条件变量:队列不空notEmpty对应的等待队列。*      如果出队成功,那就队列就不满了,就需要通知条件变量:队列不满notFull对应的等待队列。**  <p>*      我们知道 {@link Object#wait()}、{@link Object#notify()}、{@link Object#notifyAll()} 只能在 synchronized中使用*      同样 {@link Condition#await()}、{@link Condition#signal()}、{@link Condition#signalAll()} 只能在Lock&Condition中使用*      并且这三个方法一一对应, 需要注意上面三个是{@link Object}的方法,所以在下面的{@link Condition}中同样存在,千万别调用错了** @author kevin* @date 2020/7/29 23:51* @since 1.0.0** @see LinkedBlockingQueue#takeLock* @see LinkedBlockingQueue#putLock** @see LinkedBlockingQueue#notFull* @see LinkedBlockingQueue#notEmpty*/
public class BlockedQueue {/** 创建一个公平锁 */final Lock lock = new ReentrantLock(true);/** 条件变量:队列不满 */final Condition notFull = lock.newCondition();/** 条件变量:队列不空 */final Condition notEmpty = lock.newCondition();/***  入队操作*/void enqueue() throws InterruptedException {lock.lock();try {while (队列已满) {// 等待队列不满notFull.await();}// 省略入队操作// 入队后通知可出队notEmpty.signal();} finally {lock.unlock();}}void dequeue() throws InterruptedException {lock.lock();try {while (队列已空) {// 等待队列不空notEmpty.await();}// 省略出队列操作,省略一万行// 出队列后,通知可入队notFull.signal();} finally {lock.unlock();}}
}

管程模型分为三种(Java选择了MESA管程模型):

1)、Hasen模型

Hasen模型要求notify放到最后,这样T2线程通知T1后,T2线程就结束了,然后T1执行完,这样就能保证同一时刻只有一个线程在执行。

2)、Hoare模型

Hoare模型里面,T2线程通知完T1线程后,T2马上阻塞,T1马上执行;等T1执行完之后再唤醒T2线程,也能保证同一时刻只有一个线程在执行,但是T2多了一次阻塞唤醒操作。

3)、MESA管程模型(Java使用MESA模型实现)

MESA模型中,T2唤醒T1之后,T2还是会接着执行,T1并不立即执行,仅仅是从条件变量队列到等待队列中。

好处:notify(或notifyAll)、signal(或signalAll)不用放到代码的最后,T2也没有多余的阻塞唤醒操作

坏处:T1执行的时候,可能曾经满足过条件,现在已经不能满足了,需要增加循环验证条件方式(个人理解也算是乐观锁的思想)。

MESA管程模型:

看到上图的Java MESA管程模型,就理解synchronized与看似从天而降的的Object的 wait、notify、notifyAll方法,synchronized可以修饰

1、方法块前提是括号中需要有对象即Object

2、修饰普通方法,修饰的是 Object对象

3、修饰静态方法,修饰的是 .class,可以理解为也是Object对象

所以synchronized关键字,处理的是Object对象(其子类)的对象头的状态,后续再详细分析。上中的队列模型即反汇编之后看到的monitorenter、monitorexit,底层封装了条件满足后,对队列中的 wait(等待)、notify(notifyAll 唤醒)操作。结合c底层的对应的队列,和管程对象,可以理解为下图:

ObjectMonitor() {_header = NULL;_count = 0; //记录个数_waiters = 0,_recursions = 0;_object = NULL;_owner = NULL;_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet_WaitSetLock = 0 ;_Responsible = NULL ;_succ = NULL ;_cxq = NULL ;FreeNext = NULL ;_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表_SpinFreq = 0 ;_SpinClock = 0 ;OwnerIsThread = 0 ;
}

JVM内部使用了ACC_SYNCHRONIZED访问标志位来区分一个方法或代码是否为同步,如果程序执行时发现该标志则该线会先获取synchronized锁的对象的Monitor(管程)并设置标志再执行同步程序。JVM中的同步是基于进入和退出管程(Monitor)对象实现的,每个对象实例都会有一个Monitor,并且可以和对象一个创建和销毁。java中的Monitor是由C++ 的ObjectMonitor.hpp 实现,当多个线程访问该ACC_SYNCHRONIZED时,多个线程会先放入ContentionList和_EntryList中,处于block状态的线程都会被加入到该列表。

ObjectMonitor是靠底层的Mutex Lock来实现互斥的,线程申请 Mutex 成功,则持有该 Mutex,其它线程将无法获取到该 Mutex,竞争失败的线程会再次进入 ContentionList 被挂起;线程调用wait就会释放当前持有的Mutex Lock并且当前线程进入WaitSet集合。ObjectMonitor依赖底层的操作系统实现,所以存在用户态和内核态的切换,所以性能开销比较大。

获取管程(Object Monitor)后会设置自己的标志位,已经性能开销的优化可以参见:并发编程基础 - synchronized锁优化

注意:

除非经过深思熟虑(或者有非常的把握)尽量使用Object的 notifyAll(不要使用notify)、Condition的 signalAll(不要使用signal),除非满足以下三个条件【否则可能照成某些线程的饥饿等】:

1)、所以等待线程拥有相同的等待条件

2)、所以等待线程被唤醒后,执行相同的操作

3)、只需要唤醒一个线程

并发编程基础 - MESA管程模型和synchronized原子性相关推荐

  1. python中并发编程基础1

    并发编程基础概念 1.进程. 什么是进程? 正在运行的程序就是进程.程序只是代码. 什么是多道? 多道技术: 1.空间上的复用(内存).将内存分为几个部分,每个部分放入一个程序,这样同一时间在内存中就 ...

  2. Java并发编程的艺术-Java并发编程基础

    第4章 Java并发编程基础 ​ Java从诞生开始就明智地选择了内置对多线程的支持,这使得Java语言相比同一时期的其他语言具有明显的优势.线程作为操作系统调度的最小单元,多个线程能够同时执行,这将 ...

  3. Java并发编程基础--ThreadLocal

    Java并发编程基础之ThreadLocal ​ ThreadLocal是一个线程变量,但本质上是一个以ThreadLocal对象为键.任意对象为值的存储结构,这个结构依附在线程上,线程可以根据一个T ...

  4. 【牛客网】-【并发详解】-【并发编程基础】-【原子类】

    目录 并发编程基础 原子类 参考书目: 并发编程基础 在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处 ...

  5. Scala入门到精通——第二十六节 Scala并发编程基础

    本节主要内容 Scala并发编程简介 Scala Actor并发编程模型 react模型 Actor的几种状态 Actor深入使用解析 1. Scala并发编程简介 2003 年,Herb Sutte ...

  6. java 并发编程多线程_多线程(一)java并发编程基础知识

    线程的应用 如何应用多线程 在 Java 中,有多种方式来实现多线程.继承 Thread 类.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现带 ...

  7. java并发编程入门_探讨一下!Java并发编程基础篇一

    Java并发编程想必大家都不陌生,它是实现高并发/高流量的基础,今天我们就来一起学习这方面的内容. 什么是线程?什么是进程?他们之间有什么联系? 简单来说,进程就是程序的一次执行过程,它是系统进行资源 ...

  8. 高并发编程基础(线程池基础)

    线程池简单基础介绍: Executor: Executor是Java工具类,执行提交给它的Runnable任务.该接口提供了一种基于任务运行机制的任务提交方法,包括线程使用详细信息,时序等等.Exec ...

  9. Java并发编程 基础知识学习总结

    Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容,这部分的内容我也是反复学习了好几遍才能理解.本篇博客梳理一下最近从<Java 并发编程的艺术>和他人的博客学习Java并发 ...

  10. PHP 并发编程基础和实践

    随着互联网的普及,网民越来越多,这就会造成随便的一个活动,就有可能面临高并发带来的性能问题.而 PHP 是单进程的,很容易造成性能瓶颈,所以 PHP 的并发编程实践显得格外重要.如果想要真正理解并发, ...

最新文章

  1. 12月7日 第二冲刺周期个人站立会议内容报告(第七天)
  2. tp3 默认模块 默认方法_您需要了解的有关默认方法的所有信息
  3. Cheatsheet: 2010 12.13 ~ 12.23
  4. mysql与django交互_django与mysql交互
  5. Atitit sumdoc ta index list atiitt 2008 diary 大事记v2 s222.docx Atiti. 2010---2016大事记 just world new
  6. java xstream json_详解XML,Object,Json转换与Xstream的使用
  7. C语言根号作用,c语言开根号(开根号编程)
  8. 计算机病毒属于源程序吗,计算机病毒是否是源程序吗
  9. 基于STM32的红外万能遥控器完整教程
  10. 目前云存储,主要面临哪些问题?
  11. java电影推荐系统_电影推荐系统源代码
  12. nginx or apache前端禁收录,爬虫,抓取
  13. 一.什么是java面向对象 (小白神器)
  14. Mac软件无响应怎么办?
  15. 猎黑马决策股票炒股软件 主升浪战法 短线涨停
  16. 新手如何第一次编写 “Hello World“ Windows 驱动程序 (KMDF)
  17. linux 安全审计功能,Linux安全审计命令
  18. bbr中的缩放因子BW_SCALE/BBR_SCALE
  19. win7 桌面计算机不显示器,Win7电脑显示器模糊怎么办?
  20. 交互设计师:讨论几种处理问题的方法

热门文章

  1. Python做的名片器
  2. VMware Virtual SAN管理与调试
  3. FC SAN网络存储系统的搭建
  4. 使用Java语言完成数据报之间的通信即使用udp数据传输
  5. Machine Learning Engineering Case Studies with Python notebook
  6. 成功的领导者所应具备的六种素质(转)
  7. “大厂就像一座围城...”一位年薪50w的阿里测试工程师的感慨....
  8. 调用摄像头,拍一张照片并进行传输到后端的代码。涉及到IO流,文件操作File
  9. Dynamics 365Online V9.0 OAuth认证后调web api报基础连接已关闭的问题
  10. 郭健: Linux进程调度技术的前世今生之“今生”