《并发编程的艺术》阅读笔记之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并发编程的艺术>,方腾飞等著,非常经典的一本 ...
随机推荐
- OpenCV-Python OpenCV中的K-Means聚类 | 五十八
目标 了解如何在OpenCV中使用cv.kmeans()函数进行数据聚类 理解参数 输入参数 sample:它应该是np.float32数据类型,并且每个功能都应该放在单个列中. nclusters( ...
- 负载均衡器nginx和ribbon区别
1,nginx 是服务器端的负载均衡器,所有请求发送到nginx之后,nginx通过反向代理的功能分发到不同的服务器,做负载均衡 2,ribbon是客户端的负载均衡器,他是通过将eureka注册中心上 ...
- iOS 图片的解压缩
一.图片加载的工作流 概括来说,从磁盘中加载一张图片,并将它显示到屏幕上,中间的主要工作流如下: 假设我们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,此时的图片 ...
- 面试都在问的微服务、服务治理、RPC、下一代微服务框架... 一文带你彻底搞懂!
文章每周持续更新,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 单体式应用程序 与微服务相对的另一个概念是传统的单体式应用程序( ...
- 商品spu 和 sku的关系
总结一下在目前的电商系统中的商品涉及的属性spu,sku.搞清楚两者之间的关系对表的设计非常重要 spu Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集 SK ...
- CodeForces 506B/505D Mr. Kitayuta's Technology
Portal:http://codeforces.com/problemset/problem/506/B http://codeforces.com/problemset/problem/505/D ...
- 我遇到的一个ClassNotFoundException问题
近期,使用socket进行进程间Object通信,但是总是报ClassNotFoundException错误. 查找了很多原因,均没有解决. 通过写入文件,查看Object发送的消息内容到底是何种格式 ...
- My背包九讲——01背包
文章目录 背包问题中的常用变量说明 题目 解题思路 我想要想理解最简单 01背包就是要`理解
- 谷歌 MapReduce 初探
谷歌“三驾马车”的出现,才真正把我们带入了大数据时代,毕竟没有谷歌,就没有大数据. 上次的分享,我们对谷歌的其中一驾宝车 GFS 进行了管中窥豹,虽然只见得其中一斑,但是也能清楚的知道 GFS 能够把 ...
- Shell:homework
1.判断/etc/inittab文件是否大于100行,如果大于,则显示”/etc/inittab is a big file.”否则显示”/etc/inittab is a small file.”# ...