深夜!小胖问我,什么是自旋锁?怎么使用?适用场景是啥?

简介: 深夜!小胖问我,什么是自旋锁?怎么使用?适用场景是啥?

自旋锁 & 非自旋锁


什么是自旋?字面意思是 "自我旋转" 。在 Java 中也就是循环的意思,比如 for 循环,while 循环等等。那自旋锁顾名思义就是「线程循环地去获取锁」


非自旋锁,也就是普通锁。获取不到锁,线程就进入阻塞状态。等待 CPU 唤醒,再去获取。


自旋锁 & 非自旋锁的执行流程


想象以下场景:某线程去获取锁(可能是自旋锁 or 非自旋锁),然而锁现在被其他线程占用了。它两获取锁的执行流程就如下图所示:


640.png


  • 自旋锁:一直占用 CPU 的时间片去循环获取锁,直到获取到为止。


  • 非自旋锁:当前线程进入阻塞,CPU 可以去干别的事情。等待 CPU 唤醒了,线程才去获取非自旋锁。


自旋锁有啥好处?


  • 阻塞 & 唤醒线程都是需要资源开销的,如果线程要执行的任务并不复杂。这种情况下,切换线程状态带来的开销比线程执行的任务要大。


  • 而很多时候,我们的任务往往比较简单,简单到线程都还没来得及切换状态就执行完毕。这时我们选择自旋锁明显是更加明智的。


  • 所以,自旋锁的好处就是「用循环去不停地尝试获取锁,让线程始终处于 Runnable 状态,节省了线程状态切换带来的开销」


Java 中的自旋锁


在 Java 1.5 版本及以上的并发包中,也就是 java.util.concurrent 的包中,里面的原子类基本都是自旋锁的实现。我们看看做常用的 AtomicInteger 类,它里面有个 getAndIncrement 方法,源码如下:


640.png


getAndIncrement 也是直接调用 nsafe 的 getAndAddInt 方法,从下面源码可以看出这个方法直接就是做了一个 do-while 的循环。「这个循环就是一个自旋操作,如果在修改过程中遇到了其他线程竞争导致没修改成功的情况,就会 while 循环里进行死循环,直到修改成功为止」


640.png

自旋锁有啥坏处?


  • 虽然避免了线程切换的开销,但是它在避免线程切换开销的同时也带来了新的开销,因为它需要不停得去尝试获取锁。如果这把锁一直不能被释放,那么这种尝试只是无用的尝试,会白白浪费处理器资源。


  • 虽然刚开始自旋锁的开销大于线程切换。但是随着时间一直递增,总会超过线程切换的开销。


适用场景是啥?


首先我们知道自旋锁的好处就是能减少线程切换状态的开销;坏处就是如果一直旋下去,自旋开销会比线程切换状态的开销大得多。知道优缺点,那我们的适用场景就很简单了:


  • 并发不能太高,避免一直自旋不成功


  • 线程执行的同步任务不能太复杂,耗时比较短


面试官:手写一个可重入的自旋锁呗


在面试的时候经常会遇到让你实现一个可重入的自旋锁这种问题,小伙伴们还是得了解思路。为了引入自旋特性,我们使用 AtomicReference 类提供一个可以原子读写的对象引用变量。


定义一个加锁方法,如果有其他线程已经获取锁,当前线程将进入自旋,如果还是已经持有锁的线程获取锁,那就是重入。


定义一个解锁方法,解锁的话,只有持有锁的线程才能解锁,解锁的逻辑思维将 count-1,如果 count == 0,则是把当前持有锁线程设置为 null,彻底释放锁。


源码如下:


package com.nasus.thread.lock.Spin;
import java.util.concurrent.atomic.AtomicReference;
/**
 * 实现一个可重入的自旋锁
 */
public class ReentrantSpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();
    //重入次数
    private int count = 0;
    public void lock() {
        Thread t = Thread.currentThread();
        if (t == owner.get()) {
            ++count;
            return;
        }
        //自旋获取锁
        while (!owner.compareAndSet(null, t)) {
            System.out.println("自旋了");
        }
    }
    public void unlock() {
        Thread t = Thread.currentThread();
        //只有持有锁的线程才能解锁
        if (t == owner.get()) {
            if (count > 0) {
                --count;
            } else {
                //此处无需CAS操作,因为没有竞争,因为只有线程持有者才能解锁
                owner.set(null);
            }
        }
    }
    public static void main(String[] args) {
        ReentrantSpinLock spinLock = new ReentrantSpinLock();
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取自旋锁");
            spinLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                spinLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放了了自旋锁");
            }
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}


从结果我们可以看出,前面一直打印 "自旋了",说明 CPU 一直在尝试获取锁。PS:如果你们电脑不好的话,在这期间风扇会加速的,因为 CPU 一直在工作。


640.png


相关文章
|
5月前
|
安全 easyexcel 数据库
Doug Lea大师的佳作CopyOnWriteArrayList,用不好能坑死你!
【5月更文挑战第14天】Doug Lea大师的佳作CopyOnWriteArrayList,用不好能坑死你!
48 4
|
5月前
|
存储 缓存 安全
面试官:小伙子,能聊明白JMM给你SSP!我:嘚吧嘚吧一万字,直接征服面试官!
面试官:小伙子,能聊明白JMM给你SSP!我:嘚吧嘚吧一万字,直接征服面试官!
46 1
|
5月前
|
消息中间件 安全 算法
通透!从头到脚讲明白线程锁
线程锁在分布式应用中是重中之重,当谈论线程锁时,通常指的是在多线程编程中使用的同步机制,它可以确保在同一时刻只有一个线程能够访问共享资源,从而避免竞争条件和数据不一致性问题。
226 0
|
存储 安全 Python
python多线程------>这个玩意很哇塞,你不来看看吗
python多线程------>这个玩意很哇塞,你不来看看吗
|
NoSQL Java Redis
得不到你的心,就用“分布式锁”锁住你的人 码农在囧途
朋友,如果喜欢,就去表白吧,不要因为害羞,更不要因为自卑,如果现在你都还不敢表白,那么多年后,再回头来看的时候,你可能会为曾经的胆小而后悔,也可能会为错过一个人而心中久久不能释怀,所以,大胆一点,即使失败也无所谓,至少我们曾经做过,做过了就无怨无悔,在人生这条道路上,时光稍纵即逝,我们应该把握好眼前的一切,爱是一种力量,更是一种内心的慰藉,冲吧!不要因为钱不够,不要因为容貌不出中国,更不要因为身世不显赫,你只要足够勇敢,这一切都是附加品!
107 0
|
监控 安全 算法
这次锁面试题的连环16问,差点就跪了
这次锁面试题的连环16问,差点就跪了
194 0
|
Java 关系型数据库 MySQL
【浅尝高并发编程】接私活差点翻车
作为一名本本分分的练习时长两年半的Java练习生,一直深耕在业务逻辑里,对并发编程的了解仅仅停留在八股文里。一次偶然的机会,接到一个私活,核心逻辑是写一个 定时访问api把数据持久化到数据库的小服务。
166 0
J3
|
机器学习/深度学习 存储 缓存
有图有真相的Java内存模型基础,你好意思不看嘛!
有图有真相的Java内存模型基础
J3
147 0
有图有真相的Java内存模型基础,你好意思不看嘛!
|
人工智能 安全 数据挖掘
这么一搞,再也不怕线程打架了
假如我们需要处理一个文本文件,里面有 100万行数据,需要对每条数据做处理,比如将每行数据的数字做一个运算,放入到另一个文件里。
141 0
这么一搞,再也不怕线程打架了
|
Java 编译器 调度
重生之我在人间敲代码_Java并发基础_原子性问题之互斥锁
原子性问题的源头是线程切换,如果能够禁用线程切换那就能解决这个问题。而操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换。