Java并发编程总结(一)Syncronized解析

Wesley13
• 阅读 497

Syncronized解析

作用:

(1)确保线程互斥的访问同步代码

(2)保证共享变量的修改能够及时可见

(3)有效解决重排序问题。

用法:

(1)修饰普通方法(锁是当前实例对象)

(2)修饰静态方法(锁是当前对象的Class对象)

(3)修饰代码块(锁是Synchonized括号里配置的对象)

底层实现原理:

方法和代码块都是基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。

(1)代码块同步是使用monitorenter和monitorexit指令实现,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处

每个对象有一个监视器锁(monitor)。线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

执行monitorexit的线程必须是锁对象所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

(2)方法同步是使用ACC_SYNCHRONIZED标示符实现的

相对于普通方法,常量池中多了ACC_SYNCHRONIZED标示符。当方法调用时,将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

讲jdk1.6锁优化前的前置知识

锁的状态保存在java对象头中,那对象头是什么?

对象在堆内存中存储的布局可以分为3块区域:对象头、实例数据、对齐补充。(jvm知识)

对象头结构如下:

Java并发编程总结(一)Syncronized解析

CAS:Compare and Swap,比较并设置。用于在硬件层面上提供原子性操作。比较是否和给定的数值一致,如果一致则修改,不一致则不修改。

Synchronized在jdk1.6的优化

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的,这个成本非常高,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。

锁的状态总共有四种:

1.重量级锁(依赖操作系统的互斥锁)

2.轻量级锁(在无竞争的情况下使用CAS操作去消除同步使用的互斥量,默认开始就是这个)

3.偏向锁(在无竞争的情况下把整个同步都消除掉,连CAS操作都不做了)

4.无锁状态

随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)

锁的状态保存在java对象头中,以32位的JDK为例:

 Java并发编程总结(一)Syncronized解析

无锁和重量级(操作系统的互斥锁)不用解释了,重点解释轻量级锁和偏向锁。

轻量级锁

轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败且MarkWord没指向这个线程(指向这个线程则是重入,继续运行即可),表示其他线程占用了锁,当前线程便尝试使用自旋来获取锁,当自旋至一定次数或竞争的线程数大于2时,膨胀为重量级锁。

轻量级锁解锁:轻量级解锁时,会使用原子的CAS操作来将栈帧中DisplacedMark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

偏向锁

默认是开启的,锁对象第一次被线程获取的时候,会把标志位设置一下01,偏向模式。

偏向锁加锁过程:

当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作

偏向锁的撤销:

当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。 根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如上面介绍的轻量级锁那样执行

三种锁状态的总结

Java并发编程总结(一)Syncronized解析

相关的扩展:

  1. Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

2.上文提到的偏向锁、轻量级锁都是jdk1.5到1.6的锁优化技术中的。锁优化技术还有如下:

适应性自旋:

         互斥同步对性能最大的影响是阻塞的实现,因为挂起线程和恢复线程都是OS内核态完成的。那么使用自旋(即忙循环等待),不去阻塞地等待,这就是自旋锁技术。然而,自旋需要占用CPU时间,时间短还好,时间长了浪费性能,而适应性地根据同一个锁上的自旋时间及锁的拥有者状态决定时间,就是适应性自旋技术。

锁消除:

         即时编译期运行时,有些代码上的同步,被检测到不可能存在数据竞争的锁,进行消除。检测依据来源于逃逸分析技术。

锁粗化:

       如果一个方法里有A、B、C三句代码,都分别进行了三次Synchronized(this),其实没必要,反而增加了加锁解锁的消耗,这种情况会直接粗化为一个大Synchronized。

参考:

《深入理解Java虚拟机》

《Java并发编程艺术》

http://www.cnblogs.com/paddix/p/5405678.html

http://ifeve.com/java-synchronized/

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这