Java中的自旋锁解析
一、自旋锁介绍
什么是自旋锁
自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
为什么要使用自旋锁
多个线程对同一个变量一直使用CAS操作,那么会有大量修改操作,从而产生大量的缓存一致性流量,因为每一次CAS操作都会发出广播通知其他处理器,从而影响程序的性能。
线程自旋与线程阻塞
阻塞的缺点显而易见,线程一旦进入阻塞(Block),再被唤醒的代价比较高,性能较差。自旋的优点是线程还是Runnable的,只是在执行空代码。当然一直自旋也会白白消耗计算资源,所以常见的做法是先自旋一段时间,还没拿到锁就进入阻塞。JVM在处理synchrized实现时就是采用了这种折中的方案,并提供了调节自旋的参数。
首先来对比一下互斥锁和自旋锁。
- 互斥锁:从等待到解锁过程,线程会从block状态变为running状态,过程中有线程上下文的切换,抢占CPU等开销。
- 自旋锁:从等待到解锁过程,线程一直处于running状态,没有上下文的切换。
虽然自旋锁效率比互斥锁高,但它会存在下面两个问题
- 自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。
- 试图递归地获得自旋锁会引起死锁。递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。
由此可见,我们要慎重的使用自旋锁,自旋锁适合于锁使用者保持锁时间比较短并且锁竞争不激烈的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。
二、代码举例
/** * @author lichangyuan * @create 2021-10-08 10:50 */ public class SpinLock { public static void main(String[] args) throws InterruptedException { //设置100容量线程池 ExecutorService executorService = Executors.newFixedThreadPool(100); //计数器用于阻塞 CountDownLatch countDownLatch = new CountDownLatch(10); //创建自旋锁对象 SimpleSpinningLock simpleSpinningLock = new SimpleSpinningLock(); for (int i = 0; i < 10; i++) { executorService.execute(new Runnable() { @Override public void run() { simpleSpinningLock.lock(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程:" + Thread.currentThread().getName() + "执行"); simpleSpinningLock.unLock(); //确认已经连接完毕后再进行操作,将count值减1 countDownLatch.countDown(); } }); } //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行,没有则调用countDown则继续阻塞 countDownLatch.await(); } } class SimpleSpinningLock { /** * 持有锁的线程,null表示锁未被线程持有 */ private AtomicReference<Thread> sign = new AtomicReference<>(); /** * 调用lock方法时,如果sign当前值为null,说明自旋锁还没有被占用,将sign设置为currentThread,并进行锁定。 * 调用lock方法时,如果sign当前值不为null,说明自旋锁已经被其他线程占用,当前线程就会在while中继续循环检测。 */ public void lock() { //返回的正是执行当前代码指令的线程引用 Thread currentThread = Thread.currentThread(); //expect:它指定原子对象应为的值。 //val:如果原子整数等于期望值,则该值指定要更新的值。 while (!sign.compareAndSet(null, currentThread)) { //当ref为null的时候compareAndSet返回true,反之为false //通过循环不断的自旋判断锁是否被其他线程持有 } } /** * 调用unlock方法时,会将sign置为空,相当于释放自旋锁。 */ public void unLock() { Thread currentThread = Thread.currentThread(); //expect:它指定原子对象应为的值。 //val:如果原子整数等于期望值,则该值指定要更新的值。 sign.compareAndSet(currentThread, null); } }
总结
由于自旋锁只是在当前线程不停地执行循环体,不进行线程状态的切换,因此响应速度更快。
但当线程数不停增加时,性能下降明显,因为每个线程都需要占用CPU时间。
如果线程竞争不激烈,并且保持锁的时间很短,则适合使用自旋锁。
到此这篇关于Java中的自旋锁解析的文章就介绍到这了,更多相关Java自旋锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
springboot 如何通过SpringTemplateEngine渲染html
通过Spring的Thymeleaf模板引擎可以实现将模板渲染为HTML字符串,而不是直接输出到浏览器,这样可以对渲染后的字符串进行其他操作,如保存到文件或进一步处理,感兴趣的朋友跟随小编一起看看吧2024-10-10IDEA下Maven的pom文件导入依赖出现Auto build completed with errors的问题
这篇文章主要介绍了IDEA下Maven的pom文件导入依赖出现Auto build completed with errors,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下2020-06-06快速学习JavaWeb中监听器(Listener)的使用方法
这篇文章主要帮助大家快速学习JavaWeb中监听器(Listener)的使用方法,感兴趣的小伙伴们可以参考一下2016-09-09在Spring中利用@Order注解对bean和依赖进行排序
在Spring框架中,@Order是一个经常被忽视但非常重要的注解,在项目开发中,当我们需要维护bean的特定顺序或者存在许多相同类型的bean时,这个注解就发挥了作用,这篇文章讲的就是如何利用@Order注解对bean和依赖进行排序,需要的朋友可以参考下2023-11-11Installij IDEA install或clean项目的使用
这篇文章主要介绍了Installij IDEA install或clean项目的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-08-08
最新评论