Synchronized关键字算是Java的元老级锁了,一开始它撑起了Java的同步任务,其用法简单粗暴容易上手。可是有些与它相关的知识点仍是须要咱们开发者去深刻掌握的。好比,咱们都知道经过Synchronized锁来实现互斥功能,能够用在方法或者代码块上,那么不一样用法都是怎么实现的,以及都经历了了哪些优化等等问题都须要咱们扎实的理解。html
一般咱们能够把Synchronized用在一个方法或者代码块里,方法又有普通方法或者静态方法。java
对于普通同步方法,锁是当前实例对象,也就是thisbash
public class TestSyn{
private int i=0;
public synchronized void incr(){
i++;
}
}
复制代码
对于静态同步方法,锁是Class对象oracle
public class TestSyn{
private static int i=0;
public static synchronized void incr(){
i++;
}
}
复制代码
对于同步代码块,锁是同步代码块里的对象jvm
public class TestSyn{
private int i=0;
Object o = new Object();
public void incr(){
synchronized(o){
i++;
}
}
}
复制代码
在JVM规范中介绍了synchronized的实现原理,JVM基于进入和退出Monitor对 象来实现方法同步和代码块同步,但二者的实现细节不同。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另一种方式实现的,经过一个方法标志(flag) ACC_SYNCHRONIZED来实现的。布局
monitorenter 和 monitorexit性能
docs.oracle.com/javase/spec… (参考来源)优化
下面看下JVM规范里对moniterenter 和 monitorexit的介绍ui
Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread waits until the object is unlocked,this
每一个对象都有一个监视器(Moniter)与它相关联,执行moniterenter指令的线程将得到与objectref关联的监视器的全部权,若是另外一个线程已经拥有与objectref关联的监视器,则当前线程将等待直到对象被解锁为止。
A monitorenter instruction may be used with one or more monitorexit instructions to implement a synchronized statement in the Java programming language. The monitorenter and monitorexit instructions are not used in the implementation of synchronized methods
重点来了,上面这段介绍了两点:
先看下JVM规范里怎么说的
docs.oracle.com/javase/spec… (参考来源)
A synchronized method is not normally implemented using monitorenter and monitorexit. Rather, it is simply distinguished in the runtime constant pool by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the current thread acquires a monitor, invokes the method itself, and releases the monitor whether the method invocation completes normally or abruptly.
上面这段话主要讲了几点:
public class TestSyn {
private int i=0;
// 同步方法
public synchronized void incer(){
i++;
}
// 同步代码块
public void decr(){
synchronized (this) {
i--;
}
}
}
复制代码
能够经过反编译字节码来查看底层是怎么实现的
// 获得字节码
javac TestSyn.java
复制代码
// 反编译字节码
javap -v TestSyn.class
复制代码
同步代码块的反编译结果以下:
同步方法的反编译结果以下:
对象的内存布局
在咱们常见的HotSpot虚拟机中对象由三部分组成,分别是对象头,实例数据,以及对齐填充位。其中对象头是跟锁信息相关的部分,在对象头里会存储该对象运行时数据,包括哈希吗,GC分代年龄,锁状态(无锁,偏向锁,轻量级锁,重量级锁),是否偏向锁,偏向线程ID等信息。 存储上述这些的区域叫作Mark Word(标记词),除了这部分对象头还有一部分区域用来存储类型指针,能够经过该类型指针来定位对象的元数据信息。下面重点看下,对象头的内存布局,由于这部分是跟咱们此次相关的。
对象在内存中的表示以下图:
对象头的结构表示以下图:
下面举个抢茅坑的例子来解释一下锁升级过程。
当只有一个线程访问时叫作偏向锁
假设咱们每一个厕所都有一把钥匙,要想使用厕所首先必须得得到锁。某天上午员工甲急急忙忙的打完卡上厕所了,并在厕所门上贴了 “工号007使用中”的标签,说明目前被工号007(至关于线程id)的员工占用呢,他再次向进入的时候只要上面的标签还显示工号007,他本身能够随便进入,不须要再次上锁了,有点偏向工号007员工的意思,因此这叫偏向锁。
发生竞争的时候升级成轻量级锁 (自旋等待)
员工甲正在使用厕所的时候,又来了两我的想用厕所,但发现厕所被人使用着呢,没法得到锁。因此只能在外面等着甲出来,他们等的过程叫作“自旋”,这个叫作轻量级锁。那么又有一个问题,当甲出来以后正等着的那两我的谁活得锁呢?有两种方式,按到达的顺序来排队或者不排队,这两种均可以实现,前者叫作公平锁,后者叫作非公平锁。
自旋等待没结果的时候升级成重量级锁
但那两我的自旋一段时间以后发现甲还没出来(JDK1.6规定为10次),一直这么等也不是个法子啊,因此打算向上升级,找厕所管理员(操做系统)反馈,升级成了重量级锁了
锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁能够从偏向锁升级到轻量级锁,再升级的重量级锁。
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希玛,分代年龄针 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 重量级锁定 |
空,不须要记录信息 | 11 | GC标志 |
偏向线程ID,偏向时间戳,对象分代年龄 | 01 | 可偏向 |
偏向锁
偏向锁也是JDK 1.6中引入的一项锁优化, 引入它是为了优化在没有锁竞争场景下的锁消除。好比一段同步代码一直是由单个线程调用,在这种场景下就不必使用同步锁了,这里指的同步锁不是指synchronized,而是说没不要到操做系统层面的互斥量了。
偏向锁的偏向是指该同步代码会一直偏向第一个调用它的线程,直到有别的线程过来竞争这把锁,在第一次调用同步代码并得到锁时会在对象头和栈帧锁记录行(Lock Record)里存储偏向线程Id,该线程在此进入的时候就不须要从新申请锁了。只需检测对象头的Mark Word里是否存储着指向该线程的ID便可。
直到又有线程来竞争这把锁的时候偏向锁会撤销偏向。
轻量级锁
轻量级锁是JDK 1.6之中加入的新型锁机制, 它名字中的“轻量级”是相对于使用操做系统 互斥量来实现的传统锁而言的, 所以传统的锁机制就称为“重量级”锁。 它并非用来代替重量级锁的, 它的本意是在统的重量级锁使用操做系统互斥量产生的性能消耗。
线程在执行同步块以前,JVM会先在当前线程的栈桢中建立用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。而后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。若是成功,当前线程得到锁,若是失败,表示其余线程竞争锁,当前线程便尝试使用自旋来获取锁.一直原地自旋,若是自旋数达到10次了则升级为重量级锁。
重量级锁 竞争的线程自旋一段时间未能获取锁以后会升级为重量级锁,这个时候锁的获取与释放都会由操做系统来分配了,若是持有锁的线程释放锁以后操做系统会唤醒全部阻塞的哪些线程,并进入新一轮的争抢模式,须要注意的是这些阻塞的线程没有得到锁的优先级,也就是说synchronized锁是非公平的。除此以外synchronized对中断操做也是无感的,不会由于被中断而放弃阻塞等待,它要么获得锁要么一直阻塞。