概述

在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. prometheus远程写参数优化

    一.概述 prometheus可以通过远程存储来解决自身存储的瓶颈,所以其提供了远程存储接口,并可以通过过配置文件进行配置(prometheus.yml).一般情况下我们使用其默认的配置参数,但是为了 ...

  2. coding++:java-HashMap的负载因子为什么默认是0.75?

    本篇文章基于JDK1.8,特在此说明 1):负载因子的作用 负载因子是和扩容机制有关的,意思是如果当前容器的容量,达到了我们设定的最大值,就要开始执行扩容操作.举个例子来解释,避免小白听不懂: 比如说 ...

  3. Day13 流程控制

    Linux中的流程控制语句 一.if语句 1.单分支if条件语句 格式:if [ 条件判断式 ] then 程序     fi 注意:1.在Linux中是以if开头,fi结尾.其他地方一般是{开头,} ...

  4. 使用Gitee搭建个人图床

    使用Gitee搭建个人图床 一.前言 搭建个人的图床可以更加方便地管理个人图片,虽然Github也可以实现个人图床的搭建,但是,Github毕竟是外网,访问速度慢且不稳定.因此采用Gitee来搭建图床 ...

  5. linux下使用笔记本的相关设置

    目录 无线连接 Wi-Fi 蓝牙 触摸板 电源管理 电源管理工具 电源相关行为的响应动作 按键和盖子的响应动作 电池低电量的响应动作 处理器调整 调频工具 关闭睿频 intel_pstate 休眠配置 ...

  6. DOS命令集

    1.ASSOC显示或修改文件扩展名关联.ASSOC [.ext[=[fileType]]]  .ext      指定跟文件类型关联的文件扩展名  fileType  指定跟文件扩展名关联的文件类型键 ...

  7. LeetCode 题解 | 面试题57 - II. 和为s的连续正数序列

    题目描述 面试题57 - II. 和为s的连续正数序列 难度简单37收藏分享切换为英文关注反馈 输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数). 序列内 ...

  8. PTA | 1010 一元多项式求导 (25分)

    设计函数求一元多项式的导数.(注:xn(n为整数)的一阶导数为n*xn-1.) 输入格式: 以指数递降方式输入多项式非零项系数和指数(绝对值均为不超过1000的整数).数字间以空格分隔. 输出格式: ...

  9. API网关--Kong的实践

    1. 什么是Kong 目前互联网后台架构一般是采用微服务,或者类似微服务的形式,应用的请求通常需要访问多个后台系统.如果让每一个后台系统都实现鉴权.限流.负载均衡.审计等基础功能是不合适的,通用的做法 ...

  10. Shell:Day08.笔记

    函数:写一个代码块,用来重复调用的: 1.函数的写法格式 2.参数,在函数名后面直接加,即可:如果在外面  abc(){   函数体 $@  }  abc 1 2 3 4 5   :wq    a.s ...