线程安全和锁优化(二)

一、优化必要性

一般用到了锁,就说明存在阻塞现象,阻塞情况就很容易造成性能的降低,就算是优化之后也会比无锁的情况会差一些。
ReentrantLock中的tryLock,偏向于一种无锁的方式,因为在tryLock判断时,并不会把自己挂起。

二、自旋锁和自适应锁

之前的互斥同步手段对性能最大的影响是阻塞,使用synchronize关键字的时候,需要使线程挂起和唤醒,然而挂起线程和唤醒线程都需要转入到核心态完成。
实际上,程序不需要等待很久的时间就可以轮到下一个程序来执行了,那么这个时候就没有必要挂起线程,而是让等待的线程在获取到锁的时候,在原地循环等待,不断判断锁,是否可以被自己获取。如果有线程释放锁了,那就可以停止等待获取锁了。
但是获取锁的线程并没有执行什么任务,虽然都是在活跃的状态,我们称作busy-waiting。

1.自旋的优点

避免线程切换的开销(即一直处在用户态);

2.自旋的缺点:

(1)自旋锁是不公平的锁,它不会说去判断哪个线程等待的时间较长然后把锁释放给他,所以存在“线程饥饿”问题。
(2)若是等待的时间过长,那就会消耗cpu不做事情,cpu的使用率就下降。

3.自旋锁的变种

参考:面试必备之深入理解自旋锁

(1)TicketLock

刚刚上面也说了,自旋锁本身是不公平的,那么这个TicketLock就是来解决这个公平性的问题的,看到这个名字也很容易知道就像排队买票一样,谁的票先谁就先进去。同理线程获取锁等待的时候就获取一个ID号,下一个线程过来就会获取到ID+1,释放锁的线程就会按照这个ID由前到后地给锁。这样就实现公平了。

(2)CLHLock

CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋,获得锁。
将获取锁的线程状态借助节点(node)保存,每个线程都有一份独立的节点,这样就解决了TicketLock多处理器缓存同步的问题。

(3)MCSLock

对本地变量的节点进行循环
比较CLHLockMCSLock
1.将获取锁的线程状态借助节点(node)保存,每个线程都有一份独立的节点,这样就解决了TicketLock多处理器缓存同步的问题。
2.都是基于链表,不同的是CLHLock是基于隐式链表,没有真正的后续节点属性,MCSLock是显示链表,有一个指向后续节点的属性

4.互斥锁与自旋锁比较

1.都是为了实现保护资源共享的机制;
2.在任意时刻,这两者的锁只能保持一个;
3.获取互斥锁的时候,若是锁已经被占用,那么线程将进入休眠状态,若是获取自旋锁的线程则不会睡眠,而是会在原地一直等待锁的释放。

5.自适应的自旋

自旋的等待不能是无止境的,否则浪费CPU,所以等待需要有一定的限度,超过限定的访问次数仍然没有获取得到锁,那就应该主动一点的挂起线程,自旋默认次数是10,但是可以修改参数。
在JDK1.6引入自适应的自旋锁之后,自适应不意味着时间不固定了,而是由前一次在同一个锁上的等待时间来决定,虚拟机可以做一些判断。

三、锁消除

我们之前也讲过,比如Vector,他的内部源码基本所有都存在互斥同步的关键字synchronize,但是实际上有时候不存在数据竞争,那么java编译器就会在编译的时候消除这个锁。

四、锁粗化

我们提倡在共享数据的作用域里实行同步,并且尽可能的将同步范围缩小,但是有时候会有一连续的操作都对同一个对象加锁,那即便没有线程竞争也会产生性能上的不必要的损耗。

五、轻量级锁

相对于轻量级锁的就是重量级锁:synchronize
加锁的过程是通过CSA操作来进行的,他对于提升程序性能的依据就是,对于绝大部分的锁,整个周期内都是不存在竞争关系的,那么轻量级锁就可以使用CSA操作来避开使用互斥同步的开销,但是如果存在互斥量的开销外还额外的产生CSA操作,那么轻量级锁就会比重量级锁还要慢。
结论:轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

虚拟机的对象的内存布局

虚拟机的对象头分为两个部分:
1.用于存储对象自身的运行时数据,哈希码,GC分代年龄,占32或者64bit,这就是“Mark Word”;
2.存储指向方法区对象类型数据的指针
在这里插入图片描述

加锁过程

1.代码进入同步块,若是同步对象没有被锁定(01),虚拟机就将当前线程的栈帧中建立一个锁记录空间,用来存储Mark Word的拷贝(Displaced Mark Word)
2.虚拟机使用CSA操作尝试将对象的Mark Word更新为指向锁记录指针,若是成功,则拥有这个对象的锁了,并且Mark Word的锁标志位为00,表示处于轻量级锁状态
3.若是操作失败,则先检查对象的Mark Word是否指向当前线程的栈帧,若当前线程有这个锁了,那就进入同步块继续执行,若是没有,这说明这个锁被其他线程抢占了,这个时候Mark Word变成了10-重量级互斥指针。

解锁过程

通过CSA操作
1.若是对象的Mark Word仍指向线程的锁记录,那就用CSA操作把对象当前的Mark Word和线程赋值的Displaced Mark Word替换回来;
2.替换成功,同步完成,失败说明有其他锁试图获取该锁,那就唤醒被挂起的线程。

六、偏向锁

偏向锁是在JDK1.6引入的一项锁优化。目的是为了消除数据在无竞争下的同步原语,然后提升程序性能。
轻量级锁是在无竞争的情况下使用CSA操作进行消除同步使用的互斥量;
偏向锁:顾名思义,偏袒,在无竞争的情况下把整个同步都消除掉,偏向于第一个获得它的线程。若是该锁接下来没有被其他线程获取,持有偏向锁的线程就不需要同步。

当锁第一次被获取的时候,Mark Word标志位为01,即偏向模式,同时CSA操作把获取锁的线程的ID记录在对象的Mark Word里。
当另一个线程尝试去获取偏向锁的时候,偏向模式结束,撤销偏向后恢复到未锁定01或者轻量级锁定00。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值