Java 锁优化

Java 锁优化,第1张

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,此时会比重量级锁慢

重量级锁

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5481343.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存