《并发编程的艺术》阅读笔记之Sychronized
概述
在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的更多相关文章
- <<Java并发编程的艺术>>-阅读笔记和思维导图
最近在坚持每天阅读<>,不但做好笔记(MarkDown格式),还做好思维导图. 如果大家感兴趣,可以可以到码云上阅读笔记和到ProcessOn上阅读思维导图. 码云:https://git ...
- Java并发编程的艺术读书笔记(2)-并发编程模型
title: Java并发编程的艺术读书笔记(2)-并发编程模型 date: 2017-05-05 23:37:20 tags: ['多线程','并发'] categories: 读书笔记 --- 1 ...
- Java并发编程的艺术读书笔记(1)-并发编程的挑战
title: Java并发编程的艺术读书笔记(1)-并发编程的挑战 date: 2017-05-03 23:28:45 tags: ['多线程','并发'] categories: 读书笔记 --- ...
- 《Java并发编程的艺术》笔记
第1章 并发编程的挑战 1.1 上下文切换 CPU通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换. 减少上下文切换的方法有4种:无锁并发编程.CAS算法.使用最少线程.使 ...
- synchronized的实现原理-java并发编程的艺术读书笔记
1.synchronized实现同步的基础 Java中的每个对象都是可以作为锁,具体有3种表现. 1.对于普通同步方法,锁是当前实例对象. 2.对于静态同步方法,锁是当前类的Class对象. 3.对于 ...
- 《Java并发编程的艺术》留给自己以后看的笔记
<Java并发编程的艺术>这本书特别好,和<深入了解JAVA虚拟机>有一拼,建议做java的都看看,下面全部都是复制书中的部分内容,主要目的是做个笔记,方便以后遇到问题能找到. ...
- 读《Java并发编程的艺术》学习笔记(一)
接下来一个系列,是关于<Java并发编程的艺术>这本书的读书笔记以及相关知识点,主要是为了方便日后多次复习和防止忘记.废话不多说,直接步入主题: 第1章 并发编程的挑战 并发编程的目的是 ...
- 《Java并发编程的艺术》读书笔记:二、Java并发机制的底层实现原理
二.Java并发机制底层实现原理 这里是我的<Java并发编程的艺术>读书笔记的第二篇,对前文有兴趣的朋友可以去这里看第一篇:一.并发编程的目的与挑战 有兴趣讨论的朋友可以给我留言! 1. ...
- 《Java并发编程的艺术》读书笔记:一、并发编程的目的与挑战
发现自己有很多读书笔记了,但是一直都是自己闷头背,没有输出,突然想起还有博客圆这么个好平台给我留着位置,可不能荒废了. 此文读的书是<Jvava并发编程的艺术>,方腾飞等著,非常经典的一本 ...
随机推荐
- Redis学习笔记1-java 使用Redis(jedis)
一.远程操作Redis 1. 在windows环境下安装RedisDesktopManager 2. 打开RedisDesktopManager 3. Add New Connection 4. 右击 ...
- OpenCV-Python 立体图像的深度图 | 五十二
目标 在本节中, 我们将学习根据立体图像创建深度图. 基础 在上一节中,我们看到了对极约束和其他相关术语等基本概念.我们还看到,如果我们有两个场景相同的图像,则可以通过直观的方式从中获取深度信息.下面 ...
- OpenCV-Python 傅里叶变换 | 三十
目标 在本节中,我们将学习 使用OpenCV查找图像的傅立叶变换 利用Numpy中可用的FFT函数 傅立叶变换的某些应用程序 我们将看到以下函数:cv.dft(),cv.idft()等 理论 傅立叶变 ...
- 前端验证,jquery.validate插件
jQuery Validate 简介: jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求.该插件捆绑了一套有用 ...
- Thread---重排序
重排序 数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性.数据依赖分下列三种类型: 名称 代码示例 说明 写后读 a = 1;b = a; 写一 ...
- POJ2182 Lost Cows 题解
POJ2182 Lost Cows 题解 描述 有\(N\)(\(2 <= N <= 8,000\))头母牛,每头母牛有自己的独一无二编号(\(1..N\)). 现在\(N\)头母牛站成一 ...
- 使用RandomString方法后,结果返回相同的随机数解决办法
所遇问题: 在做超市管理系统的登录项目时,在对“随机数的产生”出现一个问题,在产生多个随机数的时候,出现了产生了多个一样的随机数,具体代码如下: /// <summary> /// 生成随 ...
- Spring装配Bean的三种方式+导入和混合配置
目录 Spring IoC与bean 基于XML的显式装配 xml配置的基本结构 bean实例的三种创建方式 依赖注入的两种方式 构造器注入方式 setter方法注入方式 利用命名空间简化xml 基于 ...
- Java Web项目bug经验202002112049
运行程序后,如果配置有问题,可能不会进代码,而直接报错.
- vulnhub~muzzybox
这个靶机是最新出的,Google了一番,发现walk trough少的可怜,最初是自己弄弄,但自己的确是菜. challenge 1.就是修改idcard.png 的内容,position princ ...