前言

在之前的文章《一文彻底搞懂面试中常问的各种“锁”》中介绍了Java中的各种“锁”,可能对于不是很了解这些概念的同学来说会觉得有点绕,所以我决定拆分出来,逐步详细的介绍一下这些锁的来龙去脉,那么这篇文章就先来会一会“自旋锁”。

正文

出现原因

在我们的程序中,如果存在着大量的互斥同步代码,当出现高并发的时候,系统内核态就需要不断的去挂起线程和恢复线程,频繁的此类操作会对我们系统的并发性能有一定影响。同时聪明的JVM开发团队也发现,在程序的执行过程中锁定“共享资源“的时间片是极短的,如果仅仅是为了这点时间而去不断挂起、恢复线程的话,消耗的时间可能会更长,那就“捡了芝麻丢了西瓜”了。

而在一个多核的机器中,多个线程是可以并行执行的。如果当后面请求锁的线程没拿到锁的时候,不挂起线程,而是继续占用处理器的执行时间,让当前线程执行一个忙循环(自旋操作),也就是不断在盯着持有锁的线程是否已经释放锁,那么这就是传说中的自旋锁了。

自旋锁开启

虽然在JDK1.4.2的时候就引入了自旋锁,但是需要使用“-XX:+UseSpinning”参数来开启。在到了JDK1.6以后,就已经是默认开启了。下面我们自己来实现一个基于CAS的简易版自旋锁。

public class SimpleSpinningLock {

    /**
* 持有锁的线程,null表示锁未被线程持有
*/
private AtomicReference<Thread> ref = new AtomicReference<>(); public void lock(){
Thread currentThread = Thread.currentThread();
while(!ref.compareAndSet(null, currentThread)){
//当ref为null的时候compareAndSet返回true,反之为false
//通过循环不断的自旋判断锁是否被其他线程持有
}
} public void unLock() {
Thread cur = Thread.currentThread();
if(ref.get() != cur){
//exception ...
}
ref.set(null);
}
}

简简单单几行代码就实现了一个简陋的自旋锁,下面我们来测试一下

public class TestLock {

    static int count  = 0;

    public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(100);
CountDownLatch countDownLatch = new CountDownLatch(100);
SimpleSpinningLock simpleSpinningLock = new SimpleSpinningLock();
for (int i = 0 ; i < 100 ; i++){
executorService.execute(new Runnable() {
@Override
public void run() {
simpleSpinningLock.lock();
++count;
simpleSpinningLock.unLock();
countDownLatch.countDown();
}
}); }
countDownLatch.await();
System.out.println(count);
}
} // 多次执行输出均为:100 ,实现了锁的基本功能

通过上面的代码可以看出,自旋就是在循环判断条件是否满足,那么会有什么问题吗?如果锁被占用很长时间的话,自旋的线程等待的时间也会变长,白白浪费掉处理器资源。因此在JDK中,自旋操作默认10次,我们可以通过参数“-XX:PreBlockSpin”来设置,当超过来此参数的值,则会使用传统的线程挂起方式来等待锁释放。

自适应自旋锁

随着JDK的更新,在1.6的时候,又出现了一个叫做“自适应自旋锁”的玩意。它的出现使得自旋操作变得聪明起来,不再跟之前一样死板。所谓的“自适应”意味着对于同一个锁对象,线程的自旋时间是根据上一个持有该锁的线程的自旋时间以及状态来确定的。例如对于A锁对象来说,如果一个线程刚刚通过自旋获得到了锁,并且该线程也在运行中,那么JVM会认为此次自旋操作也是有很大的机会可以拿到锁,因此它会让自旋的时间相对延长。但是如果对于B锁对象自旋操作很少成功的话,JVM甚至可能直接忽略自旋操作。因此,自适应自旋锁是一个更加智能,对我们的业务性能更加友好的一个锁。

结语

本来想着在一篇文章里面把“自旋锁”,“锁消除”,“锁粗化”等一些锁优化的概念都介绍完成的,但是发现可能篇幅会比较大,对于没怎么接触过这一块的同学来说理解起来会比较吃力,所以决定分开多个章节介绍,希望大家都不懂的地方可以多看几遍,慢慢体会,相信你会有所收获的。


公众号博文同步Github仓库,有兴趣的朋友可以帮忙给个Star哦,码字不易,感谢支持。

github.com/PeppaLittle…

推荐阅读

如何优化代码中大量的if/else,switch/case?

如何提高使用Java反射的效率?

Java日志正确使用姿势

论JVM爆炸的几种姿势及自救方法

有收获的话,就点个赞吧

关注「深夜里的程序猿」,分享最干的干货

轻松搞懂Java中的自旋锁的更多相关文章

  1. 一文彻底搞懂Java中的环境变量

    一文搞懂Java环境变量 记得刚接触Java,第一件事就是配环境变量,作为一个初学者,只知道环境变量怎样配,在加上各种IDE使我们能方便的开发,而忽略了其本质的东西,只知其然不知其所以然,随着不断的深 ...

  2. 一文带你看懂Java中的Lock锁底层AQS到底是如何实现的

    前言 相信大家对Java中的Lock锁应该不会陌生,比如ReentrantLock,锁主要是用来解决解决多线程运行访问共享资源时的线程安全问题.那你是不是很好奇,这些Lock锁api是如何实现的呢?本 ...

  3. 一文搞懂--Java中重写equals方法为什么要重写hashcode方法?

    Java中重写equals方法为什么要重写hashcode方法? 直接看下面的例子: 首先我们只重写equals()方法 public class Test { public static void ...

  4. 一文搞懂 Java 中的枚举,写得非常好!

    知识点 概念 enum的全称为 enumeration, 是 JDK 1.5 中引入的新特性. 在Java中,被 enum关键字修饰的类型就是枚举类型.形式如下: enum Color { RED, ...

  5. 来吧,一文彻底搞懂Java中最特殊的存在——null

    没事的时候,我并不喜欢逛 P 站,而喜欢逛 programcreek 这些技术型网站,于是那天晚上,在夜深人静的时候,我就发现了一个专注基础但不容忽视的主题.比如说:Java 中的 null 到底是什 ...

  6. 一篇文章让你搞懂Java中的静态代理和动态代理

    什么是代理模式 代理模式是常用的java设计模式,在Java中我们通常会通过new一个对象再调用其对应的方法来访问我们需要的服务.代理模式则是通过创建代理类(proxy)的方式间接地来访问我们需要的服 ...

  7. 彻底搞懂Java中equals和==的区别

    java当中的数据类型和“==”的含义: 1.基本数据类型(也称原始数据类型) :byte,short,char,int,long,float,double,boolean.他们之间的比较,应用双等号 ...

  8. 来吧,一文彻底搞懂Java中的Comparable和Comparator

    大家好,我是沉默王二,今天在逛 programcreek 的时候,我发现了一些专注细节但价值连城的主题.比如说:Java 的 Comparable 和 Comparator 是兄弟俩吗?像这类灵魂拷问 ...

  9. 分门别类总结Java中的各种锁,让你彻底记住

    概念 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁.有可能,会造成优先级反转或者饥 ...

随机推荐

  1. android 浮动窗体学习笔记及个人理解(仿360手机助手)

    很感谢原文作者 http://blog.csdn.net/guolin_blog/article/details/8689140 经自己理解 程序执行界面例如以下图: 1.程序入口界面 2.小浮动窗体 ...

  2. java集群优化——多线程下的单例模式

    在最初学习设计模式时,我为绝佳的设计思想激动不已,在以后的project中.多次融合设计模式,而在当下的设计中.我们已经觉察出了当初设计模式的高瞻远瞩.可是也有一些不足,须要我们去改进.有人说过.世界 ...

  3. 基于docker容器搭建fastdfs分布式文件系统

    本次环境的搭建参考了 https://blog.csdn.net/qq_43455410/article/details/84797814, 感谢博主. 主要流程如下: 1. 下载fastdfs镜像 ...

  4. 授权QQ登录的qq端前端页面变迁

    ac_type = 'qq' if ac_type == 'qq': myid, mypwd = qq_key xp = '/html/body/div/div/div[2]/div/div/div/ ...

  5. IP达人启示录(学会经营自己:靠软件来扩大自己的IP,或者获取很大的名声)

    在家附近的一个小公园中,一个老人每天晚上都在用水练习书法,他的字写的的确很不错,不懂书法的我,看了就感觉非常的带劲--苍劲有力.今晚再次路过的时候,就有种想和这个老人聊一聊的冲动,那么多年纪了,用书法 ...

  6. SVN服务器端的使用

    SVN服务器端的使用 1.下载VirtualSVN Server,安装好后打开,右键Repository->新建->Repository创意一个版本库.默认点击下一步,输入要创建版本库的名 ...

  7. YTU 2895: H--唱歌的鸟儿

    2895: H--唱歌的鸟儿 时间限制: 1 Sec  内存限制: 128 MB 提交: 26  解决: 10 题目描述 烟大东门有一棵大杨树,树上经常会有很多鸟儿飞来飞去.春天来了,学生物的小姜发现 ...

  8. YTU 2912: 圆柱体的C++

    2912: 圆柱体的C++ 时间限制: 1 Sec  内存限制: 128 MB 提交: 333  解决: 133 题目描述 小明的弟弟加入的C++兴趣小组,组长布置的第一个任务就是将已有的C程序改写成 ...

  9. mac系统下mysql5.7.13数据库编码查看和设置

    1.查看编码命令: mysql> show variables like '%character%'; +--------------------------+----------------- ...

  10. bzoj 1017: [JSOI2008]魔兽地图DotR【树形dp+背包】

    bzoj上是一个森林啊--? dp还是太弱了 设f[i][j][k]为到点i,合成j个i并且花费k金币能获得的最大力量值,a[i]为数量上限,b[i]为价格,p[i]为装备力量值 其实这个状态设计出来 ...