线程安全与锁优化——锁优化


一、自旋锁与自适应自旋

共享数据的共享时间只有很小一段时间,为此去进行线程挂起和恢复是不值得的。当计算机拥有一个以上处理器时,能让两个及两个以上的线程并行执行,我们就可以让后面请求锁的那个线程稍微等待一下,但不放弃处理器的执行时间,看看持有锁的线程是否很快就释放锁。为了让线程等待,只需要让线程执行一个忙循环(自旋),这就是自旋锁。

然而,当线程自旋等待一个很长时间都不释放锁的线程的时候,自旋锁可能造成处理器资源的浪费。

jdk1.6引入了自适应的自旋锁。这意味者自旋的时间就不在固定了,而是由前一次同一个锁上的自旋时间及锁的拥有者的状态来确定的

二、锁消除

锁消除

就是对不可能存在共享数据竞争的锁进行消除。

例如,对于java自动给我们程序员加上的锁进行的锁消除:

public String concatString(String s1,String s2,String s3){
	return s1+s2+s3;
}

虽然没看到同步的迹象。然而String的‘+’操作是用StringBuffer或StringBuilder实现的。如果用StringBuffer实现的话,它的append方法是个同步方法。在concatString这个方法中不存在共享数据竞争,所以这里可以进行锁消除。

三、锁粗化

由于进行频繁的互斥同步操作会导致不必要的性能损耗。所以,当虚拟机检测到一连串零碎的操作都需要对同一对象进行加锁,那么虚拟机将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。

四、轻量级锁

对于传统锁的方式,需要使用信号量,这就是重量级的锁。轻量级锁是在只有单个线程的情况下使用的,为了减少传统锁使用信号量产生的性能消耗。

扫描二维码关注公众号,回复: 4211415 查看本文章

对象头和轻量级锁有很大关系。对象头有两部分数据,一部分是存储对象自身运行时数据,如哈希码、GC分代年龄等,这部分的长度在32位到64位之间,官方称为Mark Word。第二部分是存储对象在方法区对象类型数据的指针。

4.1 轻量级锁的执行过程

当线程进入同步代码块时,如果此期间同步对象没有被锁定,虚拟机首先在当前线程的栈帧中创建一个名为锁记录(Lock Record)的区域,如下所示:
在这里插入图片描述
然后进行CAS操作,尝试将对象的Mark Word更新为指向Lock Record的指针。如果成功,那么这个线程就拥有了该对象的锁。如下图所示:
在这里插入图片描述
如果更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是则说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续指向;否则,那就要将轻量级锁膨胀为重量级锁。

最后,需要注意当存在多线程竞争资源的时候,轻量级锁会比重量级锁慢(因为发生了额外的CAS操作)。当然根据经验数据,“对于绝大部分的锁,在整个同步期间都是不存在竞争的”


五、偏向锁

轻量级锁消除了互斥量,而偏向锁消除了整个同步,连CAS操作都不用做。偏向锁的“偏”意思是偏向于第一个获取它的线程,如果在接下来的执行中,该锁没有被其他的锁尝试获取,则持有偏向锁的线程将用于不需要在进行同步。

然而,和轻量级锁一样,它同样也是带有效益权衡性质的优化。它并不一定总是对程序执行是有益的,如果程序中大多数的锁总是被多个不同的线程访问,那么偏向锁是多余的。

猜你喜欢

转载自blog.csdn.net/wobushixiaobailian/article/details/84297920