zoukankan      html  css  js  c++  java
  • 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) {...}
     
     
     
     
     
  • 相关阅读:
    Hadoop 学习笔记 (十) hadoop2.2.0 生产环境部署 HDFS HA Federation 含Yarn部署
    hadoop 2.x 安装包目录结构分析
    词聚类
    Hadoop 学习笔记 (十一) MapReduce 求平均成绩
    Hadoop 学习笔记 (十) MapReduce实现排序 全局变量
    Hadoop 学习笔记 (九) hadoop2.2.0 生产环境部署 HDFS HA部署方法
    Visual Studio Code 快捷键大全(Windows)
    Eclipse安装教程 ——史上最详细安装Java &Python教程说明
    jquery操作select(取值,设置选中)
    $.ajax 中的contentType
  • 原文地址:https://www.cnblogs.com/caoxb/p/13139137.html
Copyright © 2011-2022 走看看