一个想休息的线程:JVM到底是怎么处理锁的?怎么不让我阻塞呢?

时间:2021-02-06 00:10:11

我是一个线程,生活在JVM(Java虚拟机)中, 这一段日子过得有些无聊,整个世界似乎只有这一个人,天天忙着执行代码,想休息一下都很难。

我听说人类写的代码中有些特殊的地方,叫做临界区,比如synchronized修饰的方法或者代码块,他们非常神奇,在同一时刻JVM老大只允许一个线程进入执行。

实际上,老大设置了一把锁,抢到了这把锁就可以执行,否则只能阻塞,等待别人释放锁。

老大说,阻塞就是不用干活了,老老实实地等着就行。

竟然还有这等美事! 赶紧让我阻塞一次吧。

可是老大又说:“每次设置锁我都得和操作系统打交道,请他在内核中维护一个什么Mutex(互斥量)的东西,他还得把你们这些线程阻塞,切换,这可是一笔巨大的费用啊,所以这些锁还是少用为妙。”

我运气也不好,我不知道执行了多少代码,调用了多少函数,竟然一次也没遇到临界区!

我想也许这个程序员编程时不小心,没有考虑多线程并发的情况; 也有可能是这些程序大部分都是无状态的,多少个线程执行都没有问题。

于是我只好一直执行下去, 不知道过了多少天,我激动地发现,一个synchronized修饰的代码块终于出现了:

Account account = ...

synchronized(account){

...临界区的代码...

}

偏向锁

我满心期望别的线程已经进入了代码块,那我就可以阻塞、休息。

即使没有其他线程进入临界区,老大为我申请锁, 也得和操作系统协商什么互斥量,从用户态进入核心态,再从核心态返回用户态,总要花些功夫吧。

可是老大根本没有去找操作系统, 只是看了看这个account对象的所谓“对象头”,其中有个叫做Mark Word的东西,似乎是个什么数据结构, 里边有几个标识位,还有其他数据。

一个想休息的线程:JVM到底是怎么处理锁的?怎么不让我阻塞呢?

老大随手使用CAS操作把我的线程ID记录到了这个Mark Word当中,修改了标识位,然后告诉我说: 可以了,你现在拥有这把锁了,进去执行代码吧。

一个想休息的线程:JVM到底是怎么处理锁的?怎么不让我阻塞呢?

我惊奇地说:“老大你不和操作系统协商设置Mutex了?”

老大说:“不用了,你看现在就你一个线程进入了这个代码块,我只要记录下你的线程ID,就表示你拥有这把锁了,不用操作系统介入。”

我获得了锁,开始执行被synchronized包裹的代码块。

等到我第二次执行到这个synchronized的时候,老大只是看了一眼锁对象account的Mark Word就说:“你的线程ID还在,还持有着这个对象的锁,进入临界区执行吧。”

我连喘口气的机会都没有,只好继续执行。

老大说,这叫偏向锁,在没有别的线程竞争的时候,一直偏向我,可以让我一直执行下去。

我是多么期盼来一个新的线程来和我竞争啊!

轻量级锁

很快,机会就来了。

另外一个线程0x3704也要进入这个代码块执行,但是锁对象account 保存的是我的线程ID,他是没法进入临界区的。

我心想,我们两个至少得有一个进入阻塞状态,休息一会儿了。

但是老大还是不去操作系统协商,只是说: 我把这个偏向锁升级一下,变成一个轻量级的锁吧。

老大把锁对象account恢复成无锁状态,在我们俩的栈帧中各自分配了一个空间,叫做Lock Record, 把锁对象account的Mark Word在我们俩这里各自复制了一份,叫做Displaced Mark Word, 这名字真奇怪。

然后把我的Lock Record的地址使用CAS放到了Mark Word当中,并且把锁标志位改为00, 这其实就意味着我也已经获得了这个轻量级的锁了,可以继续进入临界区执行。

一个想休息的线程:JVM到底是怎么处理锁的?怎么不让我阻塞呢?

0x3704没有获得锁,但还是不阻塞,老大让他自旋几次,等待一会儿。

等到我退出临界区,释放锁的时候,需要把这个Displaced markd word 使用CAS复制回去。接下来他就可以加锁了。

我们两个交替着进入临界区,执行这段代码,相安无事,很少出现真正的竞争。

即使是出现了竞争,想获得锁的线程只要自旋几次,等待一会儿,锁就可能释放了。

很明显,如果没有竞争或者轻度的竞争,轻量级锁仅仅使用CAS操作和Lock record就避免了重量级互斥锁的开销,对JVM老大来说,确实是个好主意。

重量级锁

轻量级锁运行得挺好,我还是没有机会休息,终于有这么一天,0x3704 正在持有锁,在临界区辛苦地执行代码。 我自旋了好多次,0x3704还是没释放锁。 这时候JVM老大说: 自旋次数太多了,太浪费CPU了,接下来升级为重量级锁!

这个重量级锁需要操作系统的帮忙,依赖操作系统底层的Mutex Lock。

只见老大创建了一个monitor 对象, 把这个对象的地址更新到了Mark word当中。

锁升级了!

一个想休息的线程:JVM到底是怎么处理锁的?怎么不让我阻塞呢?

由于0x3704还在持有锁运行,而我终于进入了梦寐以求的状态:阻塞!  终于可以休息一下了!

仔细一想,老大煞费心机地设置了偏向锁和轻量级锁,就是为了避免阻塞,避免操作系统的介入, 这两种锁无非就是针对这两种情况:

偏向锁: 通常只有一个线程在临界区执行

轻量级锁: 可以有多个线程交替进入临界区,在竞争不激烈的时候,稍微自旋等待一下就能获得锁。

至于重量级锁,也是我最为期待的锁,那就是出现了激烈的竞争,只好让我们去阻塞休息了。

一个想休息的线程:JVM到底是怎么处理锁的?怎么不让我阻塞呢?的更多相关文章

  1. Flash图解线程池 | 阿里巴巴面试官希望问的线程池到底是什么?

    前言 前几天小强去阿里巴巴面试Java岗,止步于二面. 他和我诉苦自己被虐的多惨多惨,特别是深挖线程和线程池的时候,居然被问到不知道如何作答. 对于他的遭遇,结合他过了一面的那个嘚瑟样,我深表同情(加 ...

  2. JVM到底是什么呢

    在我们运行和调试Java程序的时候,经常会提到一个JVM的概念.那JVM到底是什么呢? JVM是Java程序的运行环境,它同时也是一个操作系统的一个应用程序.一个进程,因此他也有他自己的运行生命周期, ...

  3. 分享一个自制的 .net线程池

    扯淡 由于项目需求,需要开发一些程序去爬取一些网站的信息,算是小爬虫程序吧.爬网页这东西是要经过网络传输,如果程序运行起来串行执行请求爬取,会很慢,我想没人会这样做.为了提高爬取效率,必须使用多线程并 ...

  4. 用java制作日历,想休息的时候看一看离周末还有几天!

    呀!忙碌的每一天,都忘记了明天就是我们愉快周末了.今天没有朋友的闲聊的话,明天处在绷紧的状态呢!还有朋友提到,所有今天来跟大家分享一下用java来编写日历表,累了想休息了的时候,打开看看,还有几天到周 ...

  5. C#一个简单的关于线程的实例

    很多初学者听到线程会觉得晦涩难懂,很多资料一堆专有名词也是让人心烦意乱,本着学习加分享的态度,这里做一个简单的实例分享帮助初学者们初识多线程.  首先大概讲述一下多线程和多进程的区别,任务管理器里各种 ...

  6. 一个Linux下C线程池的实现

    什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了.如果线程创建和销毁时间相比任 ...

  7. Java并发线程池到底设置多大?

    前言 在我们日常业务开发过程中,或多或少都会用到并发的功能.那么在用到并发功能的过程中,就肯定会碰到下面这个问题 并发线程池到底设置多大呢? 通常有点年纪的程序员或许都听说这样一个说法 (其中 N 代 ...

  8. CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

    CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 主要方法 public CountDownLatch(int count); pu ...

  9. Java再学习——停止一个正在运行的线程

    关于这个问题,先了解一下Thread类方法中被废弃的那些方法.suspend(), resume(),stop()/stop(Throwable obj),destroy() 首先,stop(Thro ...

随机推荐

  1. 兼容ie[6-9]、火狐、Chrome、opera、maxthon3、360浏览器的js本地图片预览

    html代码: <div id="divPreview"> <img id="imgHeadPhoto" src="Images/H ...

  2. ios获取摄像头与相册图片

    iOS的一些设备上都安装了摄像头.现在绝大多数都有了. 在编程中,我们是用相应的东西来进行照相,录像等功能.   一.UIImagePickerController类 UIImagePickerCon ...

  3. AngularJS的使用方法

    1.第一个ng demo <div ng-app="" ng-init="name='Kevin'"> <input ng-model=&qu ...

  4. CSS权威指南学习笔记 —— HTML元素分类

    HTML文档由各种元素组成.比如,p.table.span等等.每个元素都会对文档的表现有所影响.CSS中,每个元素都会生成一个框(传说中的盒子),其中包含元素内容. 元素可以根据它的创建方式分为两种 ...

  5. 循序渐进看Java web日志跟踪(3)-Log4J的使用和配置

    之前说过关于java日志跟踪的几大主要用的框架,也说到了,其实在其中,Log4J充当着一个相当重要的角色.目前,大部分框架也都是采用的是Log4J,虽然说它已经停止了更新,作者也重新起了LogBack ...

  6. 附录B——MySQL样例表

    vendors表: 储存销售产品的供应商信息,每个供应商在这个表中有一个记录,供应商ID(vend_id)列用来匹配产品和供应商. 列名 vend_id vend_name vend_address ...

  7. CEF C&plus;&plus;环境搭建

    第一步:下载CEF 到这里下载最新版本的CEF http://cefbuilds.com/ 下载解压之后,大概会看到如下图所示的文件 cefclient:是一个比较复杂的示例代码cefsimple:是 ...

  8. SpringMVC加载配置Properties文件的几种方式

    最近开发的项目使用了SpringMVC的框架,用下来感觉SpringMVC的代码实现的非常优雅,功能也非常强大, 网上介绍Controller参数绑定.URL映射的文章都很多了,写这篇博客主要总结一下 ...

  9. xamarin 编译出现Xamarin&period;Build&period;Forms&period;Tasks&period;GetTaskAbi 无法加载的错误解决方法

    最新升级最新的vs2017后发现编译xamarin forms 会出现错误 Xamarin.Forms.Build.Tasks.GetTasksAbi task could not be loaded ...

  10. c&num; winform 获取当前程序运行根目录

    1 // 获取程序的基目录. 2 System.AppDomain.CurrentDomain.BaseDirectory 3 4 5 // 获取模块的完整路径. 6 System.Diagnosti ...