synchronized是java多线程编程的元老级角色,很多人会称之为重量级锁。
1.锁是什么?
在java对象都可以作为锁。
普通同步方法:锁是当前实例对象。
静态同步方法:锁是当前的class对象。
同步方法块:锁是sychonized括号中的对象。
2.synchronized加锁的原理
jvm基于进入和退出monitor对象来实现方法的同步和代码块的同步。
代码块的同步是使用了monitorenter和monitorexit指令实现的。在编译后将monitorenter指令插入到同步代码块的开始位置,monitorexit插入到代码块结束处和异常处。(方法同步与此不同)
3.synchronized-优化
Java SE1.6对Synchronized进行了各种优化之后,它并不那么重了。在不同的场景中引入不同的锁优化。
1.偏向锁:适用于锁没有竞争的情况,假设共享变量只有一个线程访问。如果有其他线程竞争锁,锁则会膨胀成为轻量级锁。
2.轻量级锁:适用于锁有多个线程竞争,但是在一个同步方法块周期中锁不存在竞争,如果在同步周期内有其他线程竞争锁,锁会膨胀为重量级锁。
3.重量级锁:竞争激烈的情况下使用重量级锁。
偏向锁和轻量级锁之所以会在性能上比重量级锁是因为好,本质上是因为偏向锁和轻量级锁仅仅使用了CAS。
4.java对象头结构介绍
1.非数组类型的对象对象头长度为2字宽,在32为虚拟机中即为2*32bit。(一下均以32位为例)
2.java对象头的markword默认存储对象的hashcode,分代年龄,和锁标志位。
3.markword的状态变化
5.偏向锁(转)
hotspot作者经过研究发现,大多数情况下锁不仅不存在竞争,而且总是有一个线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
1.加锁过程:
(1)初始时对象处于biasable状态,并且ThreadID为0即biasable & unbiased状态(这里不讨论epoch和age)
(2)当一个线程试图锁住一个处于biasable & unbiased状态的对象时,通过一个CAS将自己的ThreadID放置到Mark Word中相应的位置,如果CAS操作成功进入第(3)步否则进入(4)步
(3)当进入到这一步时代表当前没有锁竞争,Object继续保持biasable状态,但是这时ThreadID字段被设置成了偏向锁所有者的ID,然后进入到第(6)步
(4)当前线程执行CAS获取偏向锁失败(这一步是偏向锁的关键),表示在该锁对象上存在竞争并且这个时候另外一个线程获得偏向锁所有权。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,并从偏向锁所有者的私有Monitor Record列表中获取一个空闲的记录,并将Object设置为LightWeight Lock状态并且Mark Word中的LockRecord指向刚才持有偏向锁线程的Monitor record,最后被阻塞在安全点的线程被释放,进入到轻量级锁的执行路径中,同时被撤销偏向锁的线程继续往下执行同步代码。
(5)当一个线程试图锁住一个处于biasable & biased并且ThreadID不等于自己的ID时,这时由于存在锁竞争必须进入到第(4)步来撤销偏向锁。
(6)运行同步代码块
2.解锁过程
偏向锁解锁过程很简单,只需要测试下是否Object上的偏向锁模式是否还存在,如果存在则解锁成功不需要任何其他额外的操作。
总结:
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只能在单线程下起作用)
6.轻量级锁(转)
http://f.dataguru.cn/forum.php?mod=viewthread&tid=473580
state为轻量级锁时mark word存储的是lock record的指针,这里的lock record,其实是一块分配在线程堆栈上的空间区域。
用于CAS前,拷贝object上的mark word(为什么要拷贝,请看下文)。
第三项是重量级锁标记。后面的状态单词很有趣,inflated,译为膨胀,在这里意思其实是锁已升级到OS-level。
在本文的范围内,我们只关注第二和第三项即可。
为了能直观的理解lock,unlock与mark word之间的联系,见下面的流程图:
在图中,提到了拷贝object mark word,由于脱离了原始mark word,官方将它冠以displaced前缀,即displaced mark word(置换标记字)。
这个displaced mark word是整个轻量级锁实现的关键,在CAS中的compare就需要用它作为条件。
为什么要拷贝mark word?
其实很简单,原因是为了不想在lock与unlock这种底层操作上再加同步。
在拷贝完object mark word之后,JVM做了一步交换指针的操作,即流程中第一个橙色矩形框内容所述。
将object mark word里的轻量级锁指针指向lock record所在的stack指针,作用是让其他线程知道,该object monitor已被占用。
lock record里的owner指针指向object mark word的作用是为了在接下里的运行过程中,识别哪个对象被锁住了。
下图直观地描述了交换指针的操作。
最后一步unlock中,我们发现,JVM同样使用了CAS来验证object mark word在持有锁到释放锁之间,有无被其他线程访问。
如果其他线程在持有锁这段时间里,尝试获取过锁,则可能自身被挂起,而mark word的重量级锁指针也会被相应修改。
此时,unlock后就需要唤醒被挂起的线程。
总结:轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的