Synchronized与三种锁态

来源:互联网 发布:linux 没有nslookup 编辑:程序博客网 时间:2024/04/30 12:15

1 对象头与锁

要了解Synchronized的锁,必须知道对象头是怎么回事。因此这个锁就保存在对象头中。Hotpot虚拟机的对象头分两部分信息,第一部分用于存储对象自身的运行时数据,如HashCode,GC分代年龄等,这部分数据长度在32位和64位虚拟机中分别为32bit和64bit,它又称为“MarkWord”,它是实现锁的关键。另一部分就是用于存储指向方法区对象类型数据的指针,如果是数组的话,还有一个额外的空间储存数组长度。
对象头是与对象自己数据无关的额外储存成本,因此考虑到空间效率,MarkWord会根据自身的状态进行复用,也就是说在不同的状态下,它的储存结构不一样。它的变化状态如下所示

在32位的HotSpot虚拟机中对象未锁定的状态下,Mark Word的32bit空间中的25bit用于储存对象的哈希码,4bit用于储存对象分代年龄,2bit用于储存锁标志位,1bit固定为0。

2 自旋锁与自适应自旋

通常我们称Sychronized锁是一种重量级锁,是因为在互斥状态下,没有得到锁的线程会被挂起阻塞,而挂起线程和恢复线程的操作都需要转入内核态中完成。同时,虚拟机开发团队也注意到,许多应用上的数据锁只会持续很多的一段时间,如果为了这段时间去挂起和恢复线程是不值得的,所以引入了自旋锁。所谓的自旋,就是让没有获得锁的线程自己运行一段时间的自循环,这就是自旋锁。自旋锁在JDK6以后已经默认开启,可以通过-XX:+UseSpinning参数来开启。
但这显然并不是最好的一种方法,不挂起线程的代价就是该线程会一直占用处理器。如果锁被占用的时间很短,自旋等待的效果就会很好,反之,自旋会消耗大量处理器资源。因此,自旋的等待时间必须有一定的限度,如果超过限度还没有获得锁,就要挂起线程,这个限度默认是10次,可以使用-XX:PreBlockSpin改变。
在JDK6以后又引入了自适应自旋锁,也就说自旋的时间限度不是一个固定值了,而是由上一次同一个锁的自旋时间及锁的拥有者状态来决定。虚拟机认为,如果同一个锁对象自旋刚刚成功获得锁,那么下一次很可能获得锁,所以允许这次自旋锁自旋很长时间、而如果某个锁很少获得锁,那么以后在获取锁的过程中可能忽略到自旋过程。

3 锁的升级过程

在Java 6中为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。

4 偏向锁

偏向锁实际上是一种锁优化的,其目的是为了减少数据在无竞争情况下的性能消耗。其核心思想就是锁会偏向第一个获取它的线程,在接下来的执行过程中该锁没有其他的线程获取,则持有偏向锁的线程永远不需要再进行同步。

4.1 偏向锁的获取

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里储存锁偏向的线程ID。以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要检查当前Mark Word中储存的线程是否指向当前线程,如果成功,表示已经获得对象锁;如果检测失败,则需要再测试一下Mark Word中偏向锁的标志是否已经被置为1(表示当前锁是偏向锁):如果没有则使用CAS操作竞争锁,如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。


4.2 偏向锁的撤销

偏向锁使用一种等待竞争出现才释放锁的机制,所以当有其他线程尝试获得锁时,才会释放锁。偏向锁的撤销,需要等到安全点。它首先会暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果不处于活动状态,则将对象头设置为无锁状态;如果依然活动,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向其他线程,要么恢复到无锁或者标记对象不合适作为偏向锁(膨胀为轻量级锁),最后唤醒暂停的线程。

4.3 关闭偏向锁

偏向锁在Java运行环境中默认开启,但是不会随着程序启动立即生效,而是在启动几秒种后才激活,可以使用参数关闭延迟:
-XX:BiasedLockingStartupDelay=0 
同样可以关闭偏向锁
 -XX:UseBiasedLocking=false,那么程序默认进入轻量级锁。


5 轻量级锁

轻量级锁是JDK1.6之中加入的新型锁机制,它并不是来代替重量级锁的,他的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

5.1 轻量级锁加锁
线程在执行同步块之前,JVM会现在当前线程的栈帧中创建用于储存锁记录的空间(LockRecord),并将对象头的Mark Word信息复制到锁记录中。然后线程尝试使用CAS将对象头的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,并且对象的锁标志位转变为“00”,如果失败,表示其他线程竞争锁,当前线程便会尝试自旋获取锁。如果有两条以上的线程竞争同一个锁,那么轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态变为“10”,MarkWord中储存的就是指向重量级锁(互斥量)的指针,后面等待的线程也要进入阻塞状态。


5.2 轻量级锁解锁
轻量级锁解锁时,同样通过CAS操作将对象头换回来。如果成功,则表示没有竞争发生。如果失败,说明有其他线程尝试过获取该锁,锁同样会膨胀为重量级锁。在释放锁的同时,唤醒被挂起的线程。









0 0
原创粉丝点击