Java 锁优化

为了能够在线程之间更高效地共享数据及解决竞争问题,从而提高程序的运行效率,JVM 提供了锁优化

锁优化

锁优化有以下几种:
自旋锁和自适应自旋锁消除锁粗化, 轻量级锁偏向锁

自适应自旋锁

自旋: 如果使用阻塞线程,那么就需要挂起线程和恢复线程,如果贡献数据的锁定只持续很短的一段时间,那么为了这点时间而去挂起和恢复线程并不值得。那么就可以让后面请求锁的线程"稍等一会",但不放弃处理器,这样就是让线程执行一个 忙循环(自旋),这项技术就是自旋锁
可以参考如下代码:

ReentrantLock lock= new ReentrantLock();
while(lock.tryLock()){
}
//todo 线程的具体工作

但是自旋虽然避免了线程切换的开销,但它是要占用处理器时间的,如果锁被占用的时间特别少,那么自旋的效果就会很好,但是如果自旋时间很长,那么就会浪费处理器资源。因此,自旋等待的时间一定要有一定限度。
为了解决自旋的问题,就出现了 自适应自旋自适应意味着自旋的时间不再是固定的,而是由具体的运行情况来确定。有了自适应自旋,随着程序运行时间的增长和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越精准。
可以参考如下代码:

 ReentrantLock lock= new ReentrantLock();
int count=1;
while(lock.tryLock()){
count++;
Thread.sleep(count*1000);
}
//todo 线程的具体工作

锁消除

锁消除是指在某些情况下,加锁操作可以完全忽略。
对象的逃逸程度 从低到高是 不逃逸方法逃逸线程逃逸,如果我们判断一个锁对象,是不可能线程逃逸的,那么就可以忽略所有的同步措施而直接执行。
比如如下代码:

 public String contactString(String s1,String s2){
        StringBuffer sb =new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }

我们知道, StringBuffer 类对 append() 方法做了同步措施,使用了 synchronized修饰,也就是将 sb对象当成了锁。
经过逃逸分析,发现,sb对象的作用域被限制在该方法内,也就是说逃逸程度是 方法逃逸,其他线程是无法访问这个对象的,所以同步也是没有意义的,便会将所有同步措施忽略,就达到了 锁消除

锁粗化

如果一系列的连续操作都对同一个对象反复加锁和解锁,那么即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要地性能损耗。
像上面的 append() 方法就类似这种情况,当虚拟机探测到这样一串零碎的操作都对同一个对象进行加锁,就会把加锁同步的范围扩展到整个操作序列之外,像上面就会在 将两个append()方法放到一个同步块里执行,这样只需要加一次锁。

偏向锁

偏向锁的目的是消除数据在无竞争情况下的同步原语,也就是说消除所有和同步有关系的操作(包括加锁同步)
偏向锁会偏向于第一个获得它的线程,如果接下来的过程中,该锁一直没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步
如果要了解偏向锁的的具体原理,就需要先知道对象的内存结构:
在这里插入图片描述
可以看到,在对象头中有一个叫 Mark Word的区域,这个区域就是用来存放锁相关信息的地方。
当一个锁对象第一次被线程获取的时候,虚拟机就会将 Mark Word里的偏向模式设置为 1 ,表明这个锁对象处于偏向锁模式。同时使用CAS将 此时的线程ID记录到对象的 Mark Word里。记录之后,被记录的线程每次进入这个锁相关的代码块时,虚拟机都可以不进行任何操作(如加锁、解锁)。
一旦出现非被记录的线程尝试获取这个锁,就会立刻退出偏向模式,后续会通过轻量级锁方式执行同步

轻量级锁

轻量级锁是相对于重量级锁使用操作系统互斥量来实现的传统锁来说的。
它的设计是为了在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗.
具体原理:
在代码即将进入同步块的时候,如果还没有其他线程获取锁对象,那么就会在当前线程的 栈帧中建立一个名为 Lock Record的空间,来存储锁对象现在的 Mark Word的拷贝,然后虚拟机将使用CAS操作尝试把对象的Mark Word更新为 指向 拷贝的指针。如果这个操作成功,那么就代表这个线程拥有了这个对象的锁,并且将锁对象的锁状态标志位设置成轻量级 ,
如果这个更新失败了,那么就说明至少存在一条线程和当前线程竞争该对象,虚拟机首先会检查线程该对象 Mark Word是否指向当前线程的栈帧,如果是,那么就直接进入同步块,如果不是,那么轻量级锁就不再有效,会膨胀为 重量级锁 ,此时 Mark Word存储的就是指向 重量级锁(互斥量) 的指针,后面等待锁的线程就必须阻塞。
解锁的时候,如果当前还是轻量级,那么就直接把栈帧里的 拷贝直接替换回去就行了。如果当前是重量级就需要唤醒其他线程

如果没有线程竞争,轻量级锁就通过CAS操作成功地避免了使用互斥量的开销,但如果存在线程竞争,那么除了互斥量本身的开销之外,还额外发生了Cas,此时会比重量级锁慢

重量级锁

就是直接用操作系统提供的 互斥量 实现线程的互斥

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值