synchronize原理

 

synchronized的三种应用方式

一. 修饰实例方法,做用于当前实例加锁,进入同步代码前要得到当前实例的锁。数组

二. 修饰静态方法,做用于当前类对象加锁,进入同步代码前要得到当前类对象的锁。安全

三. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要得到给定对象。多线程

synchronized的字节码指令

  synchronized同步块使用了monitorenter和monitorexit指令实现同步,这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。jvm

  线程执行到monitorenter指令时,会尝试获取对象所对应的monitor全部权,也就是尝试获取对象的锁,而执行monitorexit,就是释放monitor的全部权。工具

synchronized的锁的原理

  两个重要的概念:一个是对象头,另外一个是monitor。布局

Java对象头

  在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头(Mark Word、Class Metadata Address)、实例数据和对齐填充;Java对象头是实现synchronized的锁对象的基础。通常而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。性能

Mark Word

  Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的
锁、偏向线程 ID、偏向时间戳等等。Java对象头通常占有两个机器码(在32位虚拟机中,1个机器码等于4字节,
也就是32bit)。学习

 

 

Class Metadata Address

  类型指针,便是对象指向它的类的元数据的指针,虚拟机经过这个指针来肯定这个对象是哪一个类的实例。测试

Array length

若是对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。优化

Monitor

  Monitor是一个同步工具,它内置于每个Object对象中,至关于一个许可证。拿到许可证便可以进行操做,没有拿到则须要阻塞等待。

  在hotspot虚拟机中,经过ObjectMonitor类来实现monitor。

 

 

synchronized锁的优化

  jdk1.6之后对synchronized的锁进行了优化,引入了偏向锁、轻量级锁,锁的级别从低到高逐步升级: 

  无锁->偏向锁->轻量级锁->重量级锁

自旋锁与自适应自旋

  线程的挂起和恢复会极大的影响开销。而且jdk官方人员发现,不少线程在等待锁的时候,在很短的一段时间就得到了锁,因此它们在线程等待的时候,并不须要把线程挂起,而是让他无目的的循环,通常设置10次。这样就避免了线程切换的开销,极大的提高了性能。

而适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他能够根据它前面线程的自旋状况,从而调整它的自旋,甚至是不通过自旋而直接挂起。

锁消除

  对不会存在线程安全的锁进行消除。

锁粗化

  若是jvm检测到有一串零碎的操做都对同一个对象加锁,将会把锁粗化到整个操做外部,如循环体。

偏向锁

  多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让其得到锁的代价更低而引入了偏向锁。

  当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,之后该线程在进入和退出同步块时不须要进行CAS操做来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。

  若是测试成功,表示线程已经得到了锁。

  若是测试失败,则须要再测试一下Mark Word中偏向锁的标识是否设置成01(表示当前是偏向锁)。

  若是没有设置,则使用CAS竞争锁。

  若是设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

轻量级锁

  引入轻量级锁的主要目的是在多线程竞争不激烈的状况下,经过CAS竞争锁,减小传统的重量级锁使用操做系统互斥量产生的性能消耗。

重量级锁

  重量级锁经过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操做系统的Mutex Lock实现,操做系统实现线程之间的切换须要从用户态到内核态的切换,切换成本很是高。

锁升级

  偏向锁升级轻量级锁:当一个对象持有偏向锁,一旦第二个线程访问这个对象,若是产生竞争,偏向锁升级为轻量级锁。

  轻量级锁升级重量级锁:通常两个线程对于同一个锁的操做都会错开,或者说稍微等待一下(自旋),另外一个线程就会释放锁。可是当自旋超过必定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程之外的线程都阻塞,防止CPU空转。

wait和notify的原理

  调用wait方法,首先会获取监视器锁,得到成功之后,会让当前线程进入等待状态进入等待队列而且释放锁。

  当其余线程调用notify后,会选择从等待队列中唤醒任意一个线程,而执行完notify方法之后,并不会立马唤醒线程,缘由是当前的线程仍然持有这把锁,处于等待状态的线程没法得到锁。必需要等到当前的线程执行完按monitorexit指令之后,也就是锁被释放之后,处于等待队列中的线程就能够开始竞争锁了。

wait和notify为何须要在synchronized里面?

  wait方法的语义有两个,一个是释放当前的对象锁、另外一个是使得当前线程进入阻塞队列,而这些操做都和监视器是相关的,因此wait必需要得到一个监视器锁。

而对于notify来讲也是同样,它是唤醒一个线程,既然要去唤醒,首先得知道它在哪里,因此就必需要找到这个对象获取到这个对象的锁,而后到这个对象的等待队列中去唤醒一个线程。