• Synchronized 监视器锁


    一、synchronized 的原理
    JVM 基于进入和推出Monitor对象来实现方法和同步代码块,但两者的实现细节不同。
    • synchronize 修饰的同步代码块:使用monitorenter 和 monitorexit 指令实现;
    • synchronize 修饰的方法并没有 monitorenter 和 monitorexit 指令 ,而取代之的是 ACC_SYNCHRONIZED标识,该标志指明了该方法是一个同步方法,从而执行相应的同步调用。
     
    Monitorenter 指令实现编译后到同步代码块开始的位置,而 monitorexit 是插入在方法结束出和异常处,JVM要保证每个monitorter 必须要有一个与之对应的monitorexit 。任何一个对象都可以和一个monitor 相关联,且当一个执行 monitorenter 指令的时候,会尝试获取synchronized 圆括号中对象中相关联的 monitor的所有权,即尝试获取这个对象的锁(这里的是重量级锁,为没有后面提到的偏向锁和轻量级锁)
     
     

    二、Monitor监视器锁
    大家是否还记得wait、notify、notifyAll?下面带大家了解一下synchronized与 wait、notify、notifyAll的底层原理。
    在Java中,每个对象都有两个池,锁(monitor)池和等待池

    锁池: 假设线程A已经拥有了某个对象的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
    等待池: 假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.

     它们之间的规则如下:

    • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
    • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
    • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
     
    在Java虚拟机(HotSpot)中,monitor是有ObjectMonitor实现的(C++实现的,位于HotSpot虚拟机源码openjdk8openjdkhotspotsrcsharevm untimeObjectMonitor.hpp文件),其主要数据结构如下
     
    ObjectMonitor() {
        _header       = NULL;
        _count        = 0;		// 记录个数
        _waiters      = 0,
        _recursions   = 0;		// 递归次数/重入次数
        _object       = NULL;
        _owner        = NULL;	// 记录当前持有锁的线程ID
        _WaitSet      = NULL;	// 等待池:处于wait状态的线程,会被加入到_WaitSet
        _WaitSetLock  = 0 ;
        _Responsible  = NULL ;
        _succ         = NULL ;
        _cxq          = NULL ;
        FreeNext      = NULL ;
        _EntryList    = NULL ;	// 锁池:处于等待锁block状态的线程,会被加入到该列表
        _SpinFreq     = 0 ;
        _SpinClock    = 0 ;
        OwnerIsThread = 0 ;
    }
    

    ObjectMonitor 中有两个队列,_WaitSet 和 _EntryList ,用来保存ObjectWaiter 对象列表,每个等待锁的线程都会被封装成ObjectWaiter 对象,_owner 指向持有ObjectMonitor 对象的线程,当多个线程同时访问同一同步代码块或者同步方法时,首先会进入 _EntryList 队列,当线程获取到monitor 后进入_Owner 区域并把 monitor中的 _Owner 变量设置为当前线程,同时monitor 中的计数器count 加1,若线程调用wait() 方法,将释放当前持有的monitor,_owner变量恢复为null,count 自减 1 ,同时该线程进入_WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。

    每个对象都有一个Monitor对象相关联,Monitor对象中记录了持有锁的线程信息、等待队列等。Monitor对象包含以下三个字段:

    • _owner 记录当前持有锁的线程ID
    • _EntryList 是一个队列,记录所有阻塞等待锁的线程(阻塞队列,锁池)
    • _WaitSet 也是一个队列,记录调用 wait() 方法并还未被通知的线程(等待池)
     
     ObjectMonitor.cpp文件中有wait、notify、notifyAll方法
    void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {...}
    
    void ObjectMonitor::notify(TRAPS) {...}
    void ObjectMonitor::notifyAll(TRAPS) {...}
     
     
     
     
     
  • 相关阅读:
    Network Simulator for P4(NSP4) src内容介绍
    解决 E: Unable to correct problems, you have held broken packages. 问题
    【旧版本】Ubuntu 14.04 下 P416编译器 p4c的安装
    Ubuntu 14.04 更新gcc版本至4.9.2
    Ubuntu 14.04 下 安装Protocol Buffers
    Ubuntu 14.04 删除软件附加依赖
    解决Floodlight界面无法显示问题
    OpenVirteX 创建简易虚拟网络
    2017年P4中国峰会北京站 会议小结
    406. Queue Reconstruction by Height
  • 原文地址:https://www.cnblogs.com/caoxb/p/13139137.html
Copyright © 2020-2023  润新知