前言:
在并发编程中,常常用到synchronized关键词,老是感受使用它会很重。随着Java SE 1.6对synchronize进行了各类优化,引入了偏向锁和轻量级锁,在某些状况下,减小了得到锁和释放锁带来得性能消耗。
java中每一个对象均可以做为一个锁,具体的表现有如下三种形式:前端
当一个线程试图访问同步代码块时,必须首先获取到锁,退出同步代码块时或抛出异常必须释放锁。
JVM基于进入与退出Monitor对象实现方法同步与代码块同步,不过二者的实现细节不太同样,可参见以下字节码所示。java
public class SynchronizedDemo { /** * 同步方法 */ public synchronized void testSynchronizedMethod () { System.out.println("test synchronized method"); } /** * 同步静态方法 */ public synchronized static void testSynchronizedStaticMethod () { System.out.println("test synchronized static method"); } /** * 方法同步块 */ public void testSynchronizedMethodBlock() { synchronized (this) { System.out.println("test synchronized method block"); } } }
进入java文件所在目录,经过命令行进行编译:javac SynchronizedDemo.java
而后同目录下经过以下命令,进行查看编译后字节码的详细信息:javap -verbose SynchronizedDemo.class
如图,任何对象有一个Monitor与之对应,线程执行到monitorenter时会尝试获取Monitor对象的全部权,即尝试获取对象上的锁。编程
Monitor做为操做系统的一种原语,具体由相应的编程语言实现。每一个Monitor对象又包括:数组
- _owner:记录当前持有的锁的线程,也能够了理解成锁的临界区
- _entrySet:一个队列,记录全部阻塞等待锁的线程
- _waitSet:一个队列,记录全部调用wait未被唤醒的线程
当一个线程访问Object锁时,会被放入_entrySet中等待,若是该线程获取到锁,成为当前锁的_owner;期间,线程逻辑上缺乏外部条件时,线程经过调用wait方法释放锁,进入到_waitSet队列,等到条件知足时,又被唤醒与_entrySet一块儿竞争_owner;这个外部条件在monitor机制中称为条件变量。安全
Java对象包括了对象头、属性字段、补齐区域等。
对象头在最前端,包括了两部分(非数组类型)或三部分(数组类型,多存在数据的长度),结构以下所示多线程
长度(32位机/64位机 bit) | 内容 | 说明 |
---|---|---|
32/64 | Mark Word | 存储对象的hashCode和锁信息等 |
32/64 | Class Metadata Address | 存储到对象类型数据的指针 |
32/32 | Array Length | 数组的长度(若是对象是数组) |
对象头的Mark Word会有指向管程Monitor的指针。
补齐区域:因为JVM要求java的对象占的内存大小应该是8bit的倍数,因此会有几个字节用于把对象的大小补齐到8bit的倍数,没有其它特别功能。
其中Mark Word的存储数据随着锁标志的变化以下:并发
java SE 1.6引入偏向锁与轻量级锁后,锁一共有4中状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁和重量级锁状态。且锁会随着竞争状况逐步升级,但不可降级(基于JVM的一个假定:“假定一旦破坏了上一级锁的升级,就认为该假定之后也不成立”)。编程语言
为了让线程获取锁的代价更低而引入偏向锁,由于多线程中,有些状况下,获取锁的线程同时只会有一个。
以下,线程1演示了偏向锁初始化的流程,线程2协助演示了偏向锁撤销的流程。性能
偏向锁默认是开启的,可以使用JVM参数关闭:-XX:-UseBiasedLocking,那么程序默认会进入轻量级锁
引入轻量级锁,为了避免申请互斥量,包括系统调用引发的内核态与用户态的切换、线程阻塞形成的线程切换等。优化
在线程中,虚拟机会在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称Displaced Mark Word。
以下,线程1与线程2演示了轻量级锁膨胀为重量级锁的流程。
内置锁在java中被抽象为监视器锁(monitor),对于重量级锁,监视器锁直接对应底层操做系统中的互斥量(mutex),这种同步成本很是高,包括系统调用引发的内核态与用户态切换、线程阻塞形成的线程切换等。
关于不一样锁的优缺点对比,以下所示
锁 | 有点 | 缺点 | 使用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不须要额外的消耗,和执行非同步方法时相比仅存在纳秒级的差距;毕竟仅第一执行CAS操做 | 若是线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步的场景 |
轻量级锁 | 竞争的线程不会阻塞,提升了程序的响应速度;相比偏向锁,获取和释放锁均执行一次CAS操做 | 若是使用得不到锁竞争的线程,会使用自旋会消耗CPU资源 | 追求响应时间,同步块执行速度很是快 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度较长 |