synchronized,是解决并发状况下数据同步访问问题的一把利刃。那么synchronized的底层原理是什么呢?下面咱们来一层一层剥开它的心,就像剥洋葱同样,看个究竟。html
synchronized关键字能够做用于方法或者代码块,最主要有如下几种使用方式,如图: java
接下来,咱们先剥开synchronized的第一层,反编译其做用的代码块以及方法。bash
public class SynchronizedTest {
public void doSth(){
synchronized (SynchronizedTest.class){
System.out.println("test Synchronized" );
}
}
}
复制代码
反编译,可得:数据结构
由图可得,添加了synchronized关键字的代码块,多了两个指令monitorenter、monitorexit。即JVM使用monitorenter和monitorexit两个指令实现同步,monitorenter、monitorexit又是怎样保证同步的呢?咱们等下剥第二层继续探索。多线程
public synchronized void doSth(){
System.out.println("test Synchronized method" );
}
复制代码
反编译,可得: 并发
由图可得,添加了synchronized关键字的方法,多了ACC_SYNCHRONIZED标记。即JVM经过在方法访问标识符(flags)中加入ACC_SYNCHRONIZED来实现同步功能。oracle
剥完第一层,反编译synchronized的方法以及代码块,咱们已经知道synchronized是经过monitorenter、monitorexit、ACC_SYNCHRONIZED实现同步的,它们三做用都是啥呢?咱们接着剥第二层:jvm
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:布局
If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
谷歌翻译一下,以下:
每一个对象都与一个monitor 相关联。当且仅当拥有全部者时(被拥有),monitor才会被锁定。执行到monitorenter指令的线程,会尝试去得到对应的monitor,以下:
每一个对象维护着一个记录着被锁次数的计数器, 对象未被锁定时,该计数器为0。线程进入monitor(执行monitorenter指令)时,会把计数器设置为1.
当同一个线程再次得到该对象的锁的时候,计数器再次自增.
当其余线程想得到该monitor的时候,就会阻塞,直到计数器为0才能成功。
能够看一下如下的图,便于理解用:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
谷歌翻译一下,以下:
monitor的拥有者线程才能执行 monitorexit指令。
线程执行monitorexit指令,就会让monitor的计数器减一。若是计数器为0,代表该线程再也不拥有monitor。其余线程就容许尝试去得到该monitor了。
能够看一下如下的图,便于理解用:
Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool’s method_info structure by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.
谷歌翻译一下,以下:
方法级别的同步是隐式的,做为方法调用的一部分。同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。
当调用一个设置了ACC_SYNCHRONIZED标志的方法,执行线程须要先得到monitor锁,而后开始执行方法,方法执行以后再释放monitor锁,当方法不论是正常return仍是抛出异常都会释放对应的monitor锁。
在这期间,若是其余线程来请求执行方法,会由于没法得到监视器锁而被阻断住。
若是在方法执行过程当中,发生了异常,而且方法内部并无处理该异常,那么在异常被抛到方法外面以前监视器锁会被自动释放。
能够看一下这个流程图:
好的,剥到这里,咱们还有一些不清楚的地方,monitor是什么呢,为何它能够实现同步呢?对象又是怎样跟monitor关联的呢?客观别急,咱们继续剥下一层,请往下看。
montor究竟是什么呢?咱们接下来剥开Synchronized的第三层,monitor是什么? 它能够理解为一种同步工具,或者说是同步机制,它一般被描述成一个对象。操做系统的管程是概念原理,ObjectMonitor是它的原理实现。
在Java虚拟机(HotSpot)中,Monitor(管程)是由ObjectMonitor实现的,其主要数据结构以下:
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 ;
}
复制代码
ObjectMonitor中几个关键字段的含义如图所示:
Java Monitor 的工做机理如图所示:
为了形象生动一点,举个例子:
synchronized(this){ //进入_EntryList队列
doSth();
this.wait(); //进入_WaitSet队列
}
复制代码
OK,咱们又剥开一层,知道了monitor是什么了,那么对象又是怎样跟monitor关联呢?各位帅哥美女们,咱们接着往下看,去剥下一层。
对象是如何跟monitor关联的呢?直接先看图:
看完上图,其实对象跟monitor怎样关联,咱们已经有个大概认识了,接下来咱们分对象内存布局,对象头,MarkWord一层层继续往下探讨。
在HotSpot虚拟机中,对象在内存中存储的布局能够分为3块区域:对象头(Header),实例数据(Instance Data)和对象填充(Padding)。
对象头主要包括两部分数据:Mark Word(标记字段)、Class Pointer(类型指针)。
Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
在32位的HotSpot虚拟机中,若是对象处于未被锁定的状态下,那么Mark Word的32bit空间里的25位用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,表示非偏向锁。其余状态以下图所示:
对象与monitor怎么关联?
事实上,只有在JDK1.6以前,synchronized的实现才会直接调用ObjectMonitor的enter和exit,这种锁被称之为重量级锁。一个重量级锁,为啥还要常用它呢? 从JDK6开始,HotSpot虚拟机开发团队对Java中的锁进行优化,如增长了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略。
何为自旋锁?
自旋锁是指当一个线程尝试获取某个锁时,若是该锁已被其余线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
为什么须要自旋锁?
线程的阻塞和唤醒须要CPU从用户态转为核心态,频繁的阻塞和唤醒显然对CPU来讲苦不吭言。其实不少时候,锁状态只持续很短一段时间,为了这段短暂的光阴,频繁去阻塞和唤醒线程确定不值得。所以自旋锁应运而生。
自旋锁应用场景
自旋锁适用于锁保护的临界区很小的状况,临界区很小的话,锁占用的时间就很短。
自旋锁一些思考
在这里,我想谈谈,为何ConcurrentHashMap放弃分段锁,而使用CAS自旋方式,其实也是这个道理。
何为锁消除?
锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,可是被检测到不可能存在共享数据竞争的锁进行削除。
锁消除一些思考
在这里,我想引伸到平常代码开发中,有一些开发者,在没并发状况下,也使用加锁。如没并发可能,直接上来就ConcurrentHashMap。
何为锁租化?
锁粗话概念比较好理解,就是将多个连续的加锁、解锁操做链接在一块儿,扩展成一个范围更大的锁。
为什么须要锁租化?
在使用同步锁的时候,须要让同步块的做用范围尽量小—仅在共享数据的实际做用域中才进行同步,这样作的目的是 为了使须要同步的操做数量尽量缩小,若是存在锁竞争,那么等待锁的线程也能尽快拿到锁。可是若是一系列的连续加锁解锁操做,可能会致使没必要要的性能损耗,因此引入锁粗话的概念。
锁租化比喻思考
举个例子,买门票进动物园。老师带一群小朋友去参观,验票员若是知道他们是个集体,就能够把他们当作一个总体(锁租化),一次性验票过,而不须要一个个找他们验票。
咱们直接以一张Synchronized洋葱图做为总结吧,若是你愿意一层一层剥开个人心。
欢迎你们关注,你们一块儿学习,一块儿讨论哈。