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

    通过上一篇我们知道通过内存屏障,在反编译的汇编语言中我们看到基于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)、只需要唤醒一个线程

 

 

 

 

 

 

 

 

 

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
libgl1-mesa-glx是一个用于Linux系统的离线软件包,用于提供OpenGL渲染库以及相关的图形驱动程序。它是Mesa 3D Graphics Library的一部分,可以用于支持3D图形渲染和硬件加速。 这个离线包可以在没有Internet连接的情况下进行安装。首先,需要从可靠的来源下载libgl1-mesa-glx的离线安装包。可以在官方的Mesa下载页面、软件仓库或其他可信的来源上找到这个离线包。确保选择与你所使用的操作系统和架构兼容的版本。 一旦离线包下载完成,你可以将其复制到目标计算机,确保它在没有网络连接的情况下可访问。然后,在目标计算机上打开终端或命令行界面。 通过使用适当的命令,例如"dpkg"或"apt",可以安装离线包。具体的命令会根据不同的Linux发行版而有所不同,所以你需要查阅相应的文档或参考操作系统的帮助文档来了解具体的安装过程。 在安装过程中,系统可能会要求输入管理员密码或进行其他确认操作,以确保安装的软件包的合法性和安全性。 一旦安装完成,你就可以在离线环境中使用libgl1-mesa-glx提供的功能和特性了。这个软件包不仅为你提供了OpenGL渲染库,还可以为你的系统提供必要的硬件驱动程序,以便支持图形加速和3D渲染。 总而言之,libgl1-mesa-glx离线包是一个用于Linux系统的软件包,可以在没有Internet连接的情况下安装。通过下载正确的离线包并按照操作系统的指示进行安装,你可以在离线环境中获得OpenGL渲染库和相关的图形驱动程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值