概述

在JDK1.6中,锁一共四种状态,级别由低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级,这是为了提高获得锁和释放锁的效率。只有重量级锁涉及到操作系统线程切换。

重量级锁

sychronized关键字是通过锁住对象头中的monitor对象来实现同步的。Monitor 的本质是,依赖于底层操作系统的 Mutex Lock 实现。操作系统实现线程之间的切换,需要从用户态到内核态的切换,切换成本非常高。修饰代码块和修饰方法分为了显式同步和隐式同步。当应用代码块的时候,从字节码反编译中可以看到,同步代码块进入开始之前和同步执行之后或者异常以后会有monitor enter 和 monitor exit指令。

而修饰方法的时候是用方法常量池中的ACC_sychronized标志来判断锁的。当某个线程访问这个方法的时候,首先会去检查是否有 ACC_SYNCHRONIZED 有的话就需要先获得对应的监视器锁才能执行。当方法结束或者中间抛出未被处理的异常的时候,监视器锁就会被释放。

无论采用哪种方式, 其本质是对一个对象的监视器(monitor)的获取,这个获取过程是排它的,也就是同一时刻只有一个线程获取到由synchronized所保护对象的监视器。

获取释放逻辑

在 Hotspot 中这些操作是通过 ObjectMonitor 来实现的,通过它提供的功能就可能做到获取锁,释放锁,阻塞中等待锁释放再去竞争锁,锁等待被唤醒等功能。

堆中的每个对象都有一个对象头,存放着minitor对象监视器对象。ObjectMonitor 中有如下几个字段:

_owner,ObjectMonitor 目前被哪个线程持有

_entryList,阻塞队列(阻塞竞争获取锁的一些线程)

_WaitSet,等待队列中的线程需要等待被唤醒(可以通过中断,singal,超时返回等)

_cxq,线程获取锁失败放入 _cxq 队列中

_recursions,线程重入次数,synchronized 是个可重入锁

当多个线程竞争同一把对象锁的时候,会将没有竞争到锁的线程放入_cxq队列中,再根据俄Qmodel的值决定是直接upark竞争锁还是放到到entryList队列中,当有一个线程竞争到对象锁,然后进入owner区域,会把monitor中的owner变量赋值为当前线程,然后计数器加一操作(synchronized 是个可重入锁),当线程调用wait方法的时候,会将锁释放,放入waitset队列中。其他线程可以持有锁。这也就是为什么wait。Notify是Object类中的方法了。

在 jdk1.6 之前,synchronized 就直接会去调用 ObjectMonitor 的 enter 方法获取锁了,然后释放锁的时候回去调用 ObjectMonitor 的 exit 方法。这被称之为重量级锁,可以看出它涉及到的操作复杂性。

所以我们可以想到,如果说同一时间本身就只有一个线程去访问它,那么就算它存在共享变量,由于不会被多线程同时访问也不存在线程安全问题,这个时候其实就不需要执行重量级加锁的过程。只需要在出现竞争的时候再使用线程安全的操作就行了,从而就引出了偏向锁和轻量级锁。

无锁

无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。上面我们介绍的CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

无锁CAS修改MarkWord成功则升级为偏向锁

偏向锁

当线程持有偏向锁时,会在Mark Word和该线程的栈帧的锁记录存储锁偏向线程的id,当有线程想要获取锁时,无需再次赋值,只要与Mark Word中的存储的偏向线程ID 与当前线程比较,如果一致的话,则获取到锁(说明当前想获取锁的线程之前就持有锁)。

如果不一致,则需要再测试mark word中偏向锁标识是否为1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS操作将偏向锁指向当前线程(个人理解:一直自旋到全局安全点)

偏向锁的升级

偏向锁撤销要等到全区安全点(这个时间点上没有正在执行的字节码),暂停当前持有偏向锁的线程,此时检查持有偏向锁的线程是否存活。则有两种情况

1、偏向锁的线程已经为终止状态,则将对象头设置为无锁状态,那么竞争线程(正在自旋)会通过cas操作来修改monitor对象头中的变量为自己的偏向锁id。 然后就又回到上述又是偏向锁线程的运行状态了。

2、如果存活,则偏向锁升级为轻量级锁,然后唤醒线程 A 执行完后续操作,其他线程则自旋获取轻量级锁。

为什么要引入偏向锁?

1、为了在无多线程竞争的情况下,尽量减少不必要的轻量级锁执行路径。

2、大多数情况下,锁不存在多线程竞争,而且总是由同一线程多次获得,没有必要进行多余的锁获取的代价,为了让线程获得锁的代价更低而引入了偏向锁。偏向锁使用了一种出现竞争才释放锁的机制,当有其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

轻量级锁

加锁

线程在执行同步块之前,虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,然后拷贝对象头中的Mark Word复制到锁记录中。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录(Lock Record)的指针,并将Lock Record里的owner指针指向对象的Mark Word。如果成功,则当前线程获取锁,如果失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁,则当前线程自旋等待。若自旋一段时间后(默认10次)后没有获得锁,此时轻量级锁会升级为重量级锁,当前线程(就是自旋一直拿不到的线程)会将Mark Word 的锁标志位变成 10(轻量级锁标志为00),当前线程(就是自旋一直拿不到的线程)会被阻塞

解锁

使用CAS操作将Lock Record中存储的原来的Mark Word内容替换回对象头,如成功,则说明没有竞争发生。如果失败(由于竞争锁膨胀成了重量锁,就是发现被其他线程修改了对象头中的锁标志),表示当前锁存在竞争,然后它释放锁并且唤醒在等待的线程,锁就会膨胀成重量级锁。

为什么要引入轻量级锁?

因为如果线程竞争不是很激烈,而且对象持有锁的时间不是很长,那么其他线程没必要直接进入阻塞队列,那样的话,会将消耗大量的系统资源,cpu从用户态到内核态(这块可以提一下操作系统进程切换),因此,可以让其他线程自旋一段时间,等待锁的释放,自旋会消耗cpu利用。

锁优缺点对比

参考资料

《java并发编程的艺术》

从偏向锁是如何升级到重量级锁的

https://blog.csdn.net/fycghy0803/article/details/74910238

《并发编程的艺术》阅读笔记之Sychronized的更多相关文章

  1. <<Java并发编程的艺术>>-阅读笔记和思维导图

    最近在坚持每天阅读<>,不但做好笔记(MarkDown格式),还做好思维导图. 如果大家感兴趣,可以可以到码云上阅读笔记和到ProcessOn上阅读思维导图. 码云:https://git ...

  2. Java并发编程的艺术读书笔记(2)-并发编程模型

    title: Java并发编程的艺术读书笔记(2)-并发编程模型 date: 2017-05-05 23:37:20 tags: ['多线程','并发'] categories: 读书笔记 --- 1 ...

  3. Java并发编程的艺术读书笔记(1)-并发编程的挑战

    title: Java并发编程的艺术读书笔记(1)-并发编程的挑战 date: 2017-05-03 23:28:45 tags: ['多线程','并发'] categories: 读书笔记 --- ...

  4. 《Java并发编程的艺术》笔记

    第1章 并发编程的挑战 1.1 上下文切换 CPU通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换. 减少上下文切换的方法有4种:无锁并发编程.CAS算法.使用最少线程.使 ...

  5. synchronized的实现原理-java并发编程的艺术读书笔记

    1.synchronized实现同步的基础 Java中的每个对象都是可以作为锁,具体有3种表现. 1.对于普通同步方法,锁是当前实例对象. 2.对于静态同步方法,锁是当前类的Class对象. 3.对于 ...

  6. 《Java并发编程的艺术》留给自己以后看的笔记

    <Java并发编程的艺术>这本书特别好,和<深入了解JAVA虚拟机>有一拼,建议做java的都看看,下面全部都是复制书中的部分内容,主要目的是做个笔记,方便以后遇到问题能找到. ...

  7. 读《Java并发编程的艺术》学习笔记(一)

    接下来一个系列,是关于<Java并发编程的艺术>这本书的读书笔记以及相关知识点,主要是为了方便日后多次复习和防止忘记.废话不多说,直接步入主题: 第1章  并发编程的挑战 并发编程的目的是 ...

  8. 《Java并发编程的艺术》读书笔记:二、Java并发机制的底层实现原理

    二.Java并发机制底层实现原理 这里是我的<Java并发编程的艺术>读书笔记的第二篇,对前文有兴趣的朋友可以去这里看第一篇:一.并发编程的目的与挑战 有兴趣讨论的朋友可以给我留言! 1. ...

  9. 《Java并发编程的艺术》读书笔记:一、并发编程的目的与挑战

    发现自己有很多读书笔记了,但是一直都是自己闷头背,没有输出,突然想起还有博客圆这么个好平台给我留着位置,可不能荒废了. 此文读的书是<Jvava并发编程的艺术>,方腾飞等著,非常经典的一本 ...

随机推荐

  1. npm git 常用命令行 记录

    1. 推出node命令行: 两次ctrl+C或者一次ctrl+D    退出终端:exit; 2.npm 常用 npm install <name>  安装包 npm install &l ...

  2. 自动驾驶研究回顾:CVPR 2019摘要

    我们相信开发自动驾驶技术是我们这个时代最大的工程挑战之一,行业和研究团体之间的合作将扮演重要角色.由于这个原因,我们一直在通过参加学术会议,以及最近推出的自动驾驶数据集和基于语义地图的3D对象检测的K ...

  3. 《Flutter 动画系列一》25种动画组件超全总结

    动画运行的原理 任何程序的动画原理都是一样的,即:视觉暂留,视觉暂留又叫视觉暂停,人眼在观察景物时,光信号传入大脑神经,需经过一段短暂的时间,光的作用结束后,视觉形象并不立即消失,这种残留的视觉称&q ...

  4. js 拖拽 鼠标事件,放大镜效果

    设置网站播放视频 只有一个是播放的 //需要引入jquery var v = $("video") v.bind("play",function(){ for( ...

  5. Markdown语法快速学习

    Markdown 简洁语法说明 0.前言 一直以来都是以word文档做笔记,存在很多问题,比如代码格式.高亮等.这次公司要求使用markdown,感觉眼前一亮,以前word的问题都得到了解决,而且可以 ...

  6. 【webpack 系列】进阶篇

    本文将继续引入更多的 webpack 配置,建议先阅读[webpack 系列]基础篇的内容.如果发现文中有任何错误,请在评论区指正.本文所有代码都可在 github 找到. 打包多页应用 之前我们配置 ...

  7. python fabric 练习记录

    https://blog.csdn.net/freeking101/article/details/81103945   fabric 域名

  8. Activiti网关--并行网关

    1.什么是并行网关 并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进 入和外出顺序流的: fork 分支: 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支. ...

  9. python:用cv2简单实现图片的水平、垂直翻转

    原图片的本地地址:D:/360Downloads/test.jpg 代码实现: # 导入cv2模块 import cv2 # 给出本地图片的地址 img_dir="D:/360Downloa ...

  10. HTTP Session例子

    HTTP协议是“一次性单向”协议.服务端不能主动连接客户端,只能被动等待并答复客户端请求.客户端连接服务端,发出一个HTTP Request,服务端处理请求,并且返回一个HTTP Response给客 ...