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虚拟机源码\openjdk8\openjdk\hotspot\src\share\vm\runtime\ObjectMonitor.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) {...}

 

 
 
 
 
 
posted @ 2020-06-16 09:07  cao_xiaobo  阅读(613)  评论(0编辑  收藏  举报