自旋锁 & 非自旋锁

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

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

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

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

自旋锁

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

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

自旋锁有啥好处?

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

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

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

Java 中的自旋锁

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

getAndIncrement

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

unsafe.getAndAddInt

自旋锁有啥坏处?

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

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

适用场景是啥?

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

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

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

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

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

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

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

源码如下:

package com.nasus.thread.lock.Spin;

import java.util.concurrent.atomic.AtomicReference;

/**

* 实现一个可重入的自旋锁

*/

public class ReentrantSpinLock{

private AtomicReference 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 一直在工作。

运行结果

java适应性自旋锁_深夜!小胖问我,什么是自旋锁?怎么使用?适用场景是啥?...相关推荐

  1. 存储过程没有执行完后没有释放锁_面试必问---synchronized实现原理及锁升级过程你懂吗?...

    synchronized实现原理及锁升级过程 前言: synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差 ...

  2. 为什么不用mysql版本号加锁_面试必问的Mysql事务和锁,你真的了解吗?

    前言 本文内容 事务的定义和作用,隔离级别 MVCC 是什么,快照读和加锁读 锁分类,行锁,意向锁,怎么查看 Mysql 锁的信息 悲观锁和乐观锁的使用场景 Mysql 的版本为 8.0.17. 事务 ...

  3. 悲观锁和乐观锁_带你了解MySQL中的乐观锁与悲观锁

    在并发控制编程中锁是一个非常重要的概念,锁对于数据和业务一致性的保证起到关键作用,锁可以是程序层面的,也可以是数据库层面的,今天本文就通过MySQL来说明悲观锁与乐观锁两种常见的锁机制. 悲观锁 悲观 ...

  4. lock是悲观锁还是乐观锁_图文并茂的带你彻底理解悲观锁与乐观锁

    点击上方蓝色字体,选择"设置星标" 优质文章,第一时间送达 文章转自:Hollis 原创:安静的boy 这是一篇介绍悲观锁和乐观锁的入门文章.旨在让那些不了解悲观锁和乐观锁的小白们 ...

  5. mysql limit锁_我所理解的MySQL五:锁及加锁规则

    mysql教程栏目介绍MySQL的第五篇文章,关于锁及加锁规则. MySQL 系列的第五篇,主要内容是锁(Lock),包括锁的粒度分类.行锁.间隙锁以及加锁规则等. MySQL 引入锁的目的是为了解决 ...

  6. java执行sql文件_面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他

    初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.MyBatis 能够支持 ...

  7. java反射 虚拟机优化_面试官问我:Java反射是什么?我回答竟然不上来......

    每天凌晨00点00分,第一时间与你相约 每日英文 We all have moments of desperation. But if we can face them head on, that's ...

  8. 注会和java哪个更难_总有人问我:中级和注会哪个难?现在统一回复

    原标题:总有人问我:中级和注会哪个难?现在统一回复 中级会计与注会既有联系又有区别,在就业方向上相辅相成,在学习的时候切记不要混淆方向,二者在内容上的考查有很大的不同! 一.注会的科目覆盖了中级的科目 ...

  9. java随机数扔色子_我如何问用户是否希望使用netbeans在Java中进行每个骰子掷骰后继续游戏...

    小编典典 扫描仪可用于提示用户询问他/她是否要继续. 您甚至可以跟踪编号.掷骰子的次数. import java.util.Scanner; public class DiceGame { publi ...

最新文章

  1. java拷贝压缩文件_Android java, 快速文件拷贝,文件压缩,获得系统时间 | 学步园...
  2. 倒计时两天丨NeurIPS 2020预讲会:7位智源青年科学家,21场报告
  3. Python (2) 除法
  4. Android Studio 分析器详解
  5. Codeforces 1153 C Serval and Parenthesis Sequence
  6. CentOS7.5下搭建zabbix3.4监控
  7. angular 拼接html 事件无效
  8. java中的action是指什么_Struts2【开发Action】知识要点
  9. 干货实战|基于Elastic Stack的日志分析系统
  10. lua --- 表操作
  11. delphi webbrowser 经常使用的演示样本
  12. 中琅领跑条码打印软件如何导入CDR文件
  13. 使用Cmder替换cmd,让你的开发飞起来
  14. cad自动标注界址点_CAD自带“块属性”即可实现自动标注坐标
  15. 【MATLAB信号处理】连续时间信号与系统的频域分析
  16. UDP编程与Socket
  17. Java 从入门到放弃?
  18. 搜索引擎的基本工作原理
  19. tensorflow 77 tensorflow android版本demo win10 下 编译
  20. 一种便携式导弹飞控系统外场实时仿真测试系统设计

热门文章

  1. weblogic .NoClassDefFoundError: Could not initialize class sun.awt.X11Graphi
  2. Ubuntu 学习系列-安装Flash播放器
  3. 组装电脑配置单报价_怎么选择组装电脑?牢记这四点永不吃亏,第四点最重要...
  4. 【论文写作】SSM房屋租赁系统如何写设计总结
  5. 注塑机摆放间距多少合适_垃圾分类盛行,生产塑料环卫垃圾桶的注塑机怎么选?...
  6. keepalived 多个应用_Keepalived高可用软件概述
  7. 无法下载linux系统的驱动精灵,【驱动精灵和搜狗输入法 For Linux哪个好用】驱动精灵和搜狗输入法 For Linux对比-ZOL下载...
  8. mysql简介博客_mysql简介
  9. ElasticSearch权威指南学习(索引管理)
  10. 剑指offer-序列化二叉树