目录

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详解相关推荐

  1. 【java】java 关键字: synchronized详解

    1.概述 转载:关键字: synchronized详解 [Java]Synchronized 有几种用法 [java] 从hotspot底层对象结构理解锁膨胀升级过程 [java]动态高并发时为什么推 ...

  2. Java synchronized详解

    Java synchronized详解 第一篇: 使用synchronized 在编写一个类时,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题.在Java中内置了语言级的同步原语--s ...

  3. 多线程——synchronized详解

    多线程--synchronized详解 "当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下 的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用 ...

  4. 并发编程专题——第二章(并发编程之Synchronized详解)

    日常中我们都会用到Synchronized关键字,但是面试就喜欢问这些,你说不重要吧,面试就不问了,你说重要吧,工作中除了高并发之外,很少能在业务代码中使用到的.所以笔者顶着风险,写下此篇对Synch ...

  5. 并发编程四 synchronized详解

    一 设计同步器的意义 多线程编程中,有可能会出现多个线程同时访问同一个共享.可变资源的情况,这个资源我们称之其为临界资源:这种资源可能是:对象.变量.文件等. 共享:资源可以由多个线程同时访问 可变: ...

  6. Synchronized 详解

    语法 synchronized(锁对象) // 线程1, 线程2(blocked) {临界区 } 注意 如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) ...

  7. java架构升级_java架构之路(多线程)synchronized详解以及锁的膨胀升级过程

    上几次博客,我们把volatile基本都说完了,剩下的还有我们的synchronized,还有我们的AQS,这次博客我来说一下synchronized的使用和原理. synchronized是jvm内 ...

  8. Java synchronized 详解

    下面的文字均来自其它博客和网页. 参考:http://www.jianshu.com/p/ea9a482ece5f 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重 ...

  9. Java关键字synchronized详解

    synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D ...

最新文章

  1. 教你创建高大上的多边形字体
  2. Python中函数的定义和参数传递
  3. buildroot:Linux平台构建嵌入式Linux系统的框架
  4. 使用NPO依赖的一些类库文件介绍
  5. git常用命令/mac上从零完成本地上传和下载github代码
  6. 虚拟交换系统-VSS
  7. python应纳税额计算公式_起征点上调至5000后,最新、最简个税计算Excel公式来了!...
  8. 高级前端必会手写面试题及答案
  9. 谷歌Chrome浏览器无法安装插件的解决方法
  10. uniapp微信小程序更新提醒
  11. android打开各种文件格式,笔记-Android中打开各种格式的文件(apk、word、excel、ppt、pdf、音视频、图片等)...
  12. matlab 矩阵处理,matlab矩阵处理
  13. 留学生把“中国牛排”臭豆腐带到国外,18家连锁店开遍澳洲
  14. 最新美团代付源码+支持多模板/多支付通道/全开源
  15. 科目二很难考吗?经验全在这里!
  16. 【爱贝云计费】支付接入流程
  17. 罗技驱动为什么无法识别我的鼠标?
  18. oracle脱机状态什么意思,电脑脱机状态是什么意思,电脑脱机状态怎么解除?
  19. python爬取站酷首页推荐图片
  20. Python大数据培训:绘制矢量场流线图

热门文章

  1. STM32下推式磁悬浮装置(二)原理图设计思路
  2. 凡科怎么添加html,如何给自己建立的网站添加嵌入页面?
  3. db2sql报错代码大全
  4. 编程开发:Linux网络编程学习笔记
  5. 完美解决Magic Mouse2蓝牙鼠标发飘问题
  6. adb无法识别魅族note2
  7. verilog设置24进制计数器_任意进制计数器 || 反馈复位法 反馈置数法 || 超级重点 || 数电...
  8. 【Python】phonenumbers
  9. 与哈尔滨工业大学两位信安专业同学的通信(2005年)
  10. 智慧消防水远程监测解决方案