1、概述
咱们知道在JDK1.5以前synchronized是一个重量级锁,相对于j.u.c.Lock,它会显得那么笨重,以致于咱们认为它不是那么的高效而慢慢摒弃它。安全
不过,随着后续Java版本更新对synchronized进行的各类优化后,synchronized并不会显得那么重了。好比在jdk1.7中,concurrentHashMap中使用ReenTrantLock保证线程安全,而到了jdk1.8,又换成了使用synchronized来保证线程安全。说明synchronized的性能已经能够和ReenTrantLock相差很少了多线程
2、实现原理
一、底层原理
synchronized在软件层面依赖JVM实现,而j.u.c.Lock在硬件层面依赖特殊的CPU指令实现。
synchronized加锁的代码块在编译以后,会生成monitorenter和monitorexit两个方法,对应加锁和解锁。
两个指令的执行是JVM经过调用操做系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待从新调度,会致使“用户态和内核态”两个态之间来回切换,对性能有较大影响。
二、详细说明
monitorenter:每一个对象都有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的全部权,过程以下:
- 若是monitor的进入数为0,则该线程进入monitor,而后将进入数设置为1,该线程即为monitor的全部者;
- 若是线程已经占有该monitor,只是从新进入,则进入monitor的进入数加1;
- 若是其余线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再从新尝试获取monitor的全部权;
monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的全部者。
指令执行时,monitor的进入数减1,若是减1后进入数为0,那线程退出monitor,再也不是这个monitor的全部者。其余被这个monitor阻塞的线程能够尝试去获取这个 monitor 的全部权。
三、两个队列
Monitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每一个等待锁的线程都会被封装成ObjectWaiter对象 ),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时:性能
- 首先会进入 _EntryList 集合,当线程获取到对象的monitor后,进入 _Owner区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1;
- 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒;
- 若当前线程执行完毕,也将释放monitor(锁)并复位count的值,以便其余线程进入获取monitor(锁);
四、公平性
当一个线程释放监视器时,在入口区和等待区的等待线程都会去竞争监视器,synchronized是非公平锁
五、内存结构
Monitor对象存在于每一个Java对象的对象头Mark Word中(存储的指针的指向),Synchronized锁即是经过这种方式获取锁的,也是为何Java中任意对象能够做为锁的缘由。
3、Java虚拟机对synchronize的优化
一、锁的状态
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,锁能够从偏向锁升级到轻量级锁,再升级的重量级锁。可是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。测试
二、自旋锁和自适应自旋锁
线程的阻塞和唤醒须要cpu进行用户态和内核态的切换,切换过程会消耗cpu资源。若是占用锁的时间很是短,切换锁消耗的资源就得不尝试。优化
因此在这种状况下,引入了自旋锁和自适应自旋锁(循环必定的次数判断锁是否已经释放)操作系统
三、偏向锁
在大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让同一线程得到锁的代价更低,引进了偏向锁。线程
偏向锁使用CAS加锁,代替了比较笨重的线程阻塞方式。而且在成功获取锁以后标记偏向线程,避免了同一线程屡次加锁频繁进行CAS操做。指针
CAS的全称为Compare-And-Swap,是一条CPU的原子指令,其做用是让CPU比较后原子地更新某个位置的值,通过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用对象
加锁处理流程队列
- 检测Mark Word是否为可偏向状态,便是否为偏向锁1,锁标识位为01;
- 若为可偏向状态,则测试线程ID是否为当前线程ID,若是是,则执行步骤(5),不然执行步骤(3);
- 若是测试线程ID不为当前线程ID,则经过CAS操做竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,不然执行线程(4);
- 经过CAS竞争锁失败,证实当前存在多线程竞争状况,当到达全局安全点,得到偏向锁的线程被挂起,偏向锁升级为轻量级锁,而后被阻塞在安全点的线程继续往下执行同步代码块;
- 执行同步代码块;
解锁处理过程:
- 暂停拥有偏向锁的线程;
- 判断锁对象是否还处于被锁定状态,否,则恢复到无锁状态(01),以容许其他线程竞争。是,则挂起持有锁的当前线程,并将指向当前线程的锁记录地址的指针放入对象头Mark Word,升级为轻量级锁状态(00),而后恢复持有锁的当前线程,进入轻量级锁的竞争模式;
四、轻量级锁
引入轻量级锁的主要目的是 在没有多线程竞争的前提下,减小传统的重量级锁使用操做系统互斥量产生的性能消耗
“轻量级”是相对于使用操做系统互斥量来实现的传统锁而言的。可是,首先须要强调一点的是,轻量级锁并非用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减小传统的重量级锁使用产生的性能消耗。
轻量级锁所适应的场景是线程交替执行同步块的状况,若是存在同一时间访问同一锁的状况,必然就会致使轻量级锁膨胀为重量级锁。
加锁步骤以下:
- 在线程进入同步块时,若是同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word
- 拷贝对象头中的Mark Word复制到锁记录(Lock Record)中;
- 拷贝成功后,虚拟机将使用CAS操做尝试将对象Mark Word中的Lock Word更新为指向当前线程Lock Record的指针,并将Lock record里的owner指针指向object mark word。若是更新成功,则执行步骤(4),不然执行步骤(5);
- 若是这个更新动做成功了,那么当前线程就拥有了该对象的锁,而且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态
- 若是这个更新操做失败了,虚拟机首先会检查对象Mark Word中的Lock Word是否指向当前线程的栈帧,若是是,就说明当前线程已经拥有了这个对象的锁,那就能够直接进入同步块继续执行。不然说明多个线程竞争锁,进入自旋执行(3),若自旋结束时仍未得到锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,当前线程以及后面等待锁的线程也要进入阻塞状态。
五、重量级锁
Synchronized是经过对象内部的一个叫作
监视器锁(Monitor)来实现的。
可是监视器锁本质又是依赖于底层的操做系统的Mutex Lock来实现的。而操做系统实现线程之间的切换这就须要从用户态转换到核心态,这个成本很是高,状态之间的转换须要相对比较长的时间,这就是为何Synchronized效率低的缘由。所以,这种依赖于操做系统Mutex Lock所实现的锁咱们称之为
“重量级锁”。
参考: