Java中的自旋锁解析

 更新时间:2023年10月13日 09:21:31   作者:李长渊哦  
这篇文章主要介绍了Java中的自旋锁解析,自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态,需要的朋友可以参考下

一、自旋锁介绍

什么是自旋锁

自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

为什么要使用自旋锁

多个线程对同一个变量一直使用CAS操作,那么会有大量修改操作,从而产生大量的缓存一致性流量,因为每一次CAS操作都会发出广播通知其他处理器,从而影响程序的性能。

线程自旋与线程阻塞

阻塞的缺点显而易见,线程一旦进入阻塞(Block),再被唤醒的代价比较高,性能较差。自旋的优点是线程还是Runnable的,只是在执行空代码。当然一直自旋也会白白消耗计算资源,所以常见的做法是先自旋一段时间,还没拿到锁就进入阻塞。JVM在处理synchrized实现时就是采用了这种折中的方案,并提供了调节自旋的参数。

首先来对比一下互斥锁和自旋锁。

  • 互斥锁:从等待到解锁过程,线程会从block状态变为running状态,过程中有线程上下文的切换,抢占CPU等开销。
  • 自旋锁:从等待到解锁过程,线程一直处于running状态,没有上下文的切换。

虽然自旋锁效率比互斥锁高,但它会存在下面两个问题

  1. 自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。
  2. 试图递归地获得自旋锁会引起死锁。递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

由此可见,我们要慎重的使用自旋锁,自旋锁适合于锁使用者保持锁时间比较短并且锁竞争不激烈的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

二、代码举例

/**
 * @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自旋锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringMVC接收复杂集合对象(参数)代码示例

    SpringMVC接收复杂集合对象(参数)代码示例

    这篇文章主要介绍了SpringMVC接收复杂集合对象(参数)代码示例,举接收List<String>、List<User>、List<Map<String,Object>>、User[]、User(bean里面包含List)几种较为复杂的集合参数,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • java实现PPT转PDF出现中文乱码问题的解决方法

    java实现PPT转PDF出现中文乱码问题的解决方法

    这篇文章主要为大家详细介绍了java实现PPT转PDF出现中文乱码问题的解决方法,进行了详细的问题分析,需要的朋友可以参考下
    2015-11-11
  • mybatis plus的3种查询方式(小结)

    mybatis plus的3种查询方式(小结)

    这篇文章主要介绍了mybatis plus的3种查询方式(小结),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • jedis的return行为源码解析

    jedis的return行为源码解析

    这篇文章主要为大家介绍了jedis的return行为源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Springboot 如何关闭自动配置

    Springboot 如何关闭自动配置

    这篇文章主要介绍了Springboot 如何关闭自动配置的操作,具有很好的开车价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java数据类型的规则

    Java数据类型的规则

    这篇文章主要介绍了Java数据类型的规则的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-12-12
  • 一文让你搞懂如何手写一个redis分布式锁

    一文让你搞懂如何手写一个redis分布式锁

    既然要搞懂Redis分布式锁,那肯定要有一个需要它的场景。高并发售票问题就是一个经典案例。本文就来利用这个场景手写一个redis分布式锁,让你彻底搞懂它
    2022-11-11
  • SpringBoot拦截器实现登录拦截的示例代码

    SpringBoot拦截器实现登录拦截的示例代码

    本文主要介绍了SpringBoot拦截器实现登录拦截,文中根据实例编码详细介绍的十分详尽,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • IDEA配置静态资源热加载操作(Springboot修改静态资源不重启)

    IDEA配置静态资源热加载操作(Springboot修改静态资源不重启)

    这篇文章主要介绍了IDEA配置静态资源热加载操作(Springboot修改静态资源不重启),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • mybatis使用xml进行增删改查代码解析

    mybatis使用xml进行增删改查代码解析

    这篇文章主要介绍了mybatis使用xml进行增删改查代码解析,分享了相关配置和代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02

最新评论