synchronized详解
目录
1 简介
2 用法
3 原理
3.1 Monitor对象
3.2 对象的内存布局
3.2.1 对象头
3.2.2 实例数据
3.2.3 对齐填充
4 synchronized优化
4.1 偏向锁
偏向锁撤销
4.2 轻量级锁
4.3 重量级锁
4.4 锁粗化
4.5 锁消除
5 锁对比
1 简介
java语言的关键字,在多线程情况下,可以保证共享资源的安全性,保证同一时刻只有一个线程去访问这个共享资源,其他线程必须等待获取该共享资源的线程执行完毕,才能获取到这个资源。synchronized是非公平的,谁先抢到谁使用。
举个例子:火车站窗口卖票(一个窗口),同一时刻只有一个能去买票,其他人必须等到前一个人买完之后离开才能继续买票,而且卖票的服务人员能时刻知道票的剩余量, 不会出现多卖的情况。
2 用法
synchronized能用到两个地方:
(1):作用到代码块,锁定的是当前变量
public void m1() {synchronized(o) {//业务逻辑}}
(2):作用到方法上,此时又有两种情况
①:实例方法:锁定的是当前对象
public synchronized void m1(){//业务逻辑}
②:静态方法:锁定的是当前类对象
public synchronized static void m1(){//业务逻辑}
下面是例子:
锁定的对象只有一个线程能够获取,其他线程阻塞等待。下面举例说明:
public class SynTest {static Object object =new Object();public static void main(String[] args) {new Thread(()->{synchronized (object){System.out.println(Thread.currentThread().getName()+"获取了线程111");try {Thread.sleep(5000);System.out.println(Thread.currentThread().getName()+"释放线程");} catch (InterruptedException e) {e.printStackTrace();}}}).start();new Thread(()->{System.out.println("我已经开启线程");synchronized (object){System.out.println(Thread.currentThread().getName()+"获取了线程222");}}).start();}
}
结果如下:
说明:Thread-0和Thread-1开始运行,但是由于Thread-0已经把object对象锁定,所以Thread-1只有等到Thread-0线程执行完毕才能得到object对象。
3 原理
在讲述原理之前,先聊聊Monitor对象和对象的内存布局。
3.1 Monitor对象
在HotSpot虚拟机中,Monitor是基于C++的ObjectMonitor类实现的,每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
monitor是线程私有的数据结构,每一个线程都有一个可用monitor列表,同时还有一个全局的可用列表,先来看monitor的内部:
Owner:初始时为NULL表示当前没有任何线程拥有该monitor,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;
EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor失败的线程。
RcThis:表示blocked或waiting在该monitor上的所有线程的个数。
Nest:用来实现重入锁的计数。
HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值:0表示没有需要唤醒的线程,1表示要唤醒一个继任线程来竞争锁。
这里不在深入了。
JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。
根据虚拟机规范的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;相应地,在执行monitorexit指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
看看下面的例子:
public class LockCoarsening {static Object object = new Object();public static void main(String[] args) {lockCoarsening();}public static void lockCoarsening(){synchronized (object){System.out.println("................");}}
}
使用命令javap -c SysTest.class进行反编译,结果如下:
通过反编译,可以看出以前结论正确。
3.2 对象的内存布局
3.2.1 对象头
对象头由两部分组成:
- Mark Word:存储自身的运行时数据,例如 HashCode、GC 年龄、锁的状态等内容。(8字节)
- Klass Pointer:类型指针指向它的类元数据的指针。(开启指针压缩:4字节,不开启:8字节,默认开启)
如果是数组,还会记录数组的长度。
Mark Word:
3.2.2 实例数据
比如类里面的变量,父类的变量等。
3.2.3 对齐填充
第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。对象头正好是8字节的倍数(1倍或者2倍),因此当对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
面试题:Object o = new Object()占多少字节呢?
总结:synchronized用的锁是存在Java对象头里的,在 java 虚拟机中,线程一旦进入到被synchronized修饰的方法或代码块时,指定的锁对象通过某些操作将对象头中的LockWord指向monitor 的起始地址与之关联,同时monitor 中的Owner存放拥有该锁的线程的唯一标识,确保一次只能有一个线程执行该部分的代码,线程在获取锁之前不允许执行该部分的代码。
4 synchronized优化
jdk1.6之前,synchronized一直是重量级的锁,在一般情况下,是非常影响性能的,因为监视器锁(Monitor)本质是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的 ,挂起和恢复线程都是需要转入内核态去完成,阻塞和唤醒线程需要操作系统切换CPU状态去实现,这种是非常消耗资源的。自jdk1.6引入了偏向锁、轻量级锁、自适应自旋锁、锁粗化、锁消除等进行优化。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。
4.1 偏向锁
在大部分情况,锁并不存在多线程竞争,而是始终只有一个线程获取,频繁的获取锁和释放锁都会带来不小的开销。
由于一个线程获取到,这个线程就是锁的偏向线程,只有第一次获取到该锁的时候,才会记录下偏向线程的ID,并且当前线程一直持有该锁,当再次执行同步代码时,只需比较对象头记录的是不是自己,如果是,就不需要再次获取锁了,执行同步代码,如果不是,说明不是一个线程竞争锁,也就不是归一个线程所有,此时可能升级为轻量级锁,一般偏向锁的线程会一直持有该锁,只有发生竞争偏向锁时,持有偏向锁的线程才会释放锁,并不会主动去释放。
锁的记录存储在对象头中,54位存储线程指针作为表示,锁的标志位也会变成101,默认开启偏向锁,但是有四秒的延迟,使用-XX:BiasedLockingStartupDelay=0命令关闭,下面验证偏向锁:
public class Test {static Object object = new Object();public static void main(String[] args) {new Thread(()->{synchronized (object){System.out.println("演示偏向锁");System.out.println(ClassLayout.parseInstance(object).toPrintable());}},"T1").start();}
}
结果输出如下:
偏向锁撤销
偏向锁使用了一种等待竞争出现才会释放锁的机制。所以当其他线程尝试获取偏向锁时,持有偏向锁的线程才会释放锁。但是偏向锁的撤销需要等到全局安全点(该点没有正在执行的字节码)。同时检查持有偏向锁的线程是否存活:
如果存活,其他线程竞争,取消该锁并出现锁的升级,变成轻量级锁,此轻量级锁还是由偏向锁线程持有,其他线程自选等待获取轻量级锁
如果死亡,则将对象头状态设置为无锁,并重新偏向。
4.2 轻量级锁
在没有多线程情况下,减少重量级锁带来的资源消耗。
线程一,首先获取到轻量级锁,线程二再去获取锁,此时线程一已经占用,则线程二一直通过自旋去尝试去获取锁(自适应自旋锁:当达到一定条件还是没有获得锁,便不再自旋),成功:占有锁,修改指针和标志位,失败:锁升级。
演示:
public class Test {static Object object = new Object();public static void main(String[] args) {new Thread(()->{synchronized (object){System.out.println("演示轻量级锁");System.out.println(ClassLayout.parseInstance(object).toPrintable());}},"T1").start();new Thread(()->{synchronized (object){System.out.println("演示轻量级锁");System.out.println(ClassLayout.parseInstance(object).toPrintable());}},"T2").start();}
}
结果如下:
锁升级过程:
4.3 重量级锁
演示:
public class Tt {static Object object = new Object();public static void main(String[] args) {new Thread(()->{synchronized (object){try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ClassLayout.parseInstance(object).toPrintable());}}).start();new Thread(()->{synchronized (object){System.out.println(ClassLayout.parseInstance(object).toPrintable());}}).start();}
}
结果如下:
4.4 锁粗化
细粒度锁在影响业务的前提下,改变为粗粒度锁,来降低无意义的资源消耗。如果存在连串的一系列操作都对同一个对象反复加锁和解锁,甚至加锁操作时出现在循环体中的,那即使没有线程竞争,频繁的进行互斥同步操作也会导致不必要的性能操作。
举例:
public static StringBuffer lockCoarsening(){StringBuffer stringBuffer = new StringBuffer();for (int i = 0; i < 20; i++) {stringBuffer.append("1");}return stringBuffer;}
看看append方法:
@Overridepublic synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;}
早期:由于append方法是synchronized修饰的,所以每次调用append方法时,都要获取锁和释放锁
后期:JVM会探测到一连串细小的操作都使用同一个对象加锁,所以只会在最外面获取和释放一次锁。
4.5 锁消除
锁擦除的主要依据是来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,说明不会发生逃逸,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。
举例:
public static void lockCoarsening(){StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("1").append("2");System.out.println(stringBuffer.toString());}
stringBuffer对象的引用永远不会“逃逸”到lockCoarsening()方法之外,其他线程无法访问到它,因此,虽然这里有锁,但是可以被安全地消除掉,这段代码就会忽略append的同步,而是直接执行。
5 锁对比
如果有错误,记得提醒我修改!
参考:
关键字: synchronized详解 | Java 全栈知识体系synchronized关键字: synchronized详解 | Java 全栈知识体系
锁:锁优化(synchronized 锁升级过程、锁消除、锁粗化) - 怀梦想,致远方 - 博客园
synchronized详解相关推荐
- 【java】java 关键字: synchronized详解
1.概述 转载:关键字: synchronized详解 [Java]Synchronized 有几种用法 [java] 从hotspot底层对象结构理解锁膨胀升级过程 [java]动态高并发时为什么推 ...
- Java synchronized详解
Java synchronized详解 第一篇: 使用synchronized 在编写一个类时,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题.在Java中内置了语言级的同步原语--s ...
- 多线程——synchronized详解
多线程--synchronized详解 "当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下 的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用 ...
- 并发编程专题——第二章(并发编程之Synchronized详解)
日常中我们都会用到Synchronized关键字,但是面试就喜欢问这些,你说不重要吧,面试就不问了,你说重要吧,工作中除了高并发之外,很少能在业务代码中使用到的.所以笔者顶着风险,写下此篇对Synch ...
- 并发编程四 synchronized详解
一 设计同步器的意义 多线程编程中,有可能会出现多个线程同时访问同一个共享.可变资源的情况,这个资源我们称之其为临界资源:这种资源可能是:对象.变量.文件等. 共享:资源可以由多个线程同时访问 可变: ...
- Synchronized 详解
语法 synchronized(锁对象) // 线程1, 线程2(blocked) {临界区 } 注意 如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) ...
- java架构升级_java架构之路(多线程)synchronized详解以及锁的膨胀升级过程
上几次博客,我们把volatile基本都说完了,剩下的还有我们的synchronized,还有我们的AQS,这次博客我来说一下synchronized的使用和原理. synchronized是jvm内 ...
- Java synchronized 详解
下面的文字均来自其它博客和网页. 参考:http://www.jianshu.com/p/ea9a482ece5f 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重 ...
- Java关键字synchronized详解
synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D ...
最新文章
- 教你创建高大上的多边形字体
- Python中函数的定义和参数传递
- buildroot:Linux平台构建嵌入式Linux系统的框架
- 使用NPO依赖的一些类库文件介绍
- git常用命令/mac上从零完成本地上传和下载github代码
- 虚拟交换系统-VSS
- python应纳税额计算公式_起征点上调至5000后,最新、最简个税计算Excel公式来了!...
- 高级前端必会手写面试题及答案
- 谷歌Chrome浏览器无法安装插件的解决方法
- uniapp微信小程序更新提醒
- android打开各种文件格式,笔记-Android中打开各种格式的文件(apk、word、excel、ppt、pdf、音视频、图片等)...
- matlab 矩阵处理,matlab矩阵处理
- 留学生把“中国牛排”臭豆腐带到国外,18家连锁店开遍澳洲
- 最新美团代付源码+支持多模板/多支付通道/全开源
- 科目二很难考吗?经验全在这里!
- 【爱贝云计费】支付接入流程
- 罗技驱动为什么无法识别我的鼠标?
- oracle脱机状态什么意思,电脑脱机状态是什么意思,电脑脱机状态怎么解除?
- python爬取站酷首页推荐图片
- Python大数据培训:绘制矢量场流线图
热门文章
- STM32下推式磁悬浮装置(二)原理图设计思路
- 凡科怎么添加html,如何给自己建立的网站添加嵌入页面?
- db2sql报错代码大全
- 编程开发:Linux网络编程学习笔记
- 完美解决Magic Mouse2蓝牙鼠标发飘问题
- adb无法识别魅族note2
- verilog设置24进制计数器_任意进制计数器 || 反馈复位法 反馈置数法 || 超级重点 || 数电...
- 【Python】phonenumbers
- 与哈尔滨工业大学两位信安专业同学的通信(2005年)
- 智慧消防水远程监测解决方案