前言

在之前的文章《一文彻底搞懂面试中常问的各种“锁”》中介绍了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. Redis实践系列丨Codis数据迁移原理与优化

    Codis介绍 Codis 是一种Redis集群的实现方案,与Redis社区的Redis cluster类似,基于slot的分片机制构建一个更大的Redis节点集群,对于连接到codis的Redis客 ...

  2. 安卓版本和Api Level

    Platform Version API Level VERSION_CODE Notes Android 4.4 19 KITKAT Platform Highlights Android 4.3 ...

  3. java基础入门-建立能够多client链接的ServerSocket

    承接上一篇文章,今天谈论一下能够多client链接的ServerSocket. 这里面注意涉及到的技术点是: 1.ServerSocket 2.多线程 这次我们分成两个类来实现,先上代码: packa ...

  4. SQLServer导出单表数据

    采用生成脚本---仅数据..   如果是部分数据,可以先把部分数据备份到一个表中 select * into .. from ...

  5. is和==的区别,小数据池,编码

    1   is  和  == 的区别 1>    id( )表示我们可以通过它来查到在内存中的地址 s = "alex" lst = [1,2, 4] lst = [1, 2, ...

  6. Codeforces 757 D. Felicity's Big Secret Revealed 状压DP

    D. Felicity's Big Secret Revealed   The gym leaders were fascinated by the evolutions which took pla ...

  7. 什么是 jQuery EasyUI

    jQuery EasyUI 是一个基于 jQuery 的框架,集成了各种用户界面插件. jQuery EasyUI 框架提供了创建网页所需的一切,帮助您轻松建立站点. easyui 是一个基于 jQu ...

  8. css class嵌套

    css 代码: <style> .chose_bonus { font-size:9px;width:400px;border: 2px solid #dddddd;margin-top: ...

  9. XMU 1613 刘备闯三国之三顾茅庐(一) 【并查集】

    1613: 刘备闯三国之三顾茅庐(一) Time Limit: 1000 MS  Memory Limit: 128 MBSubmit: 99  Solved: 29[Submit][Status][ ...

  10. YTU 2837: 编程题B-狐狸算卦

    2837: 编程题B-狐狸算卦 时间限制: 1 Sec  内存限制: 128 MB 提交: 76  解决: 52 题目描述 注:本题只需要提交需要完善部分的代码,请按照C++方式提交. 小熊和狐狸是邻 ...