ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Synchronized 看一篇就够了

2021-12-03 19:58:55  阅读:127  来源: 互联网

标签:Word 一篇 Synchronized 对象 lock 就够 synchronized 线程 指针


原文:《Synchronized 看一篇就够了》

使用场景:

  1. 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。
  2. 修饰静态方法,作用于当前类加锁,进入同步代码前要获得当前类的锁。
  1. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象。

synchronized:解决的是执行控制的问题,它会阻止其它线程获取当前对象的监控锁,这样就使得当前对象中被synchronized关键字保护的代码块无法被其它线程访问,也就无法并发执行。更重要的是,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性,同时也使得先获得这个锁的线程的所有操作,都happens-before于随后获得这个锁的线程的操作

锁是存在对象头中的:

TestClass1的实例中的三块数据分别是什么?

在 JVM 中,对象在内存中分为三块区域:

  • 对象头
    • Mark Word(标记字段):用于存储对象自身的运行时数据,比如对象自己的HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit)。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
    • Class Metadata Address(也叫Klass Word。类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:
      每个Class的属性指针(即静态变量)
      每个对象的属性指针(即对象变量)
      普通对象数组的每个元素指针
      当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
    • Array length:如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据。它的长度规则和压缩规则和klass word相同,64位JVM如果开启+UseCompressedOops选项,该区域长度也将由64位压缩至32位。
  • 实例数据
    • 这部分主要是存放类的数据信息,父类的信息。
  • 对齐填充
    • 由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。
      Tip:不知道大家有没有被问过一个空对象占多少个字节?就是8个字节,是因为对齐填充的关系哈,不到8个字节对其填充会帮我们自动补齐。

Java对象的状态主要靠Mark Word来标记,以下是Java对象的5种锁状态,大部分与线程有关,下面每一行代表对象处于某状态时Mark Word的样子。

Mark Word(64bit)

锁状态

unused:25

identity_hashcode:31

unused:1

age:4

biased_lock:0

lock:01

正常/无锁

thread:54

epoch:2

unused:1

age:4

biased_lock:1

lock:01

偏向锁

ptr_to_lock_record:62

lock:00

轻量级锁

ptr_to_heavyweight_monitor:62

lock:10

重量级锁

lock:11

GC标志

上表中各部分和属性,从右往左含义如下:

  • lock:2位的锁状态标记位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。
  • biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。biased_lock和lock一起,表达的状态含义如下:
biased_lock   lock      状态

0             01        无锁

1             01        偏向锁

              00        轻量级锁

              10        重量级锁 (synchronized锁指的实际上就是他)

              11        GC标记
  • age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
  • identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。
  • thread:持有偏向锁的线程ID。
  • epoch:偏向锁的时间戳。
  • ptr_to_lock_record:6位指针,轻量级锁状态下,指向线程栈帧中锁记录的指针。
  • ptr_to_heavyweight_monitor:重量级锁状态下,指针指向的是Monitor对象(也称为管程或监视器锁)的地址

我们通常说的通过synchronized实现的同步锁,真实名称叫做重量级锁。

在jdk6 之前,通过synchronized关键字加锁时使用无差别的的重量级锁,重量级锁会造成线程排队(串行执行),并且使CPU在用户态和核心态之间频繁切换。随着对synchronized的不断优化,提出了锁升级的概念,并引入了偏向锁、轻量级锁、重量级锁。在Mark Word中,锁(lock)标志位占用2个bit,结合1个bit偏向锁(biased_lock)标志位,这样通过倒数的3位,就能用来标识当前对象持有的锁的状态,并判断出其余位存储的是什么信息。

锁升级步骤:

  1. 无锁:初期锁对象刚创建时,还没有任何线程来竞争,对象的Mark Word是无锁状态,这偏向锁标识位是0,锁状态01,说明该对象处于无锁状态(无线程竞争它)。
  2. 偏向锁:当有一个线程来竞争锁时,先用偏向锁,表示锁对象偏爱这个线程,这个线程要执行这个锁关联的任何代码,不需要再做任何检查和切换,这种竞争不激烈的情况下,效率非常高。这时Mark Word会记录自己偏爱的线程的ID,把该线程当做自己的熟人。这样就完成了从无锁升级为偏向锁的过程。
  1. 轻量级锁:当有两个线程开始竞争这个锁对象,情况发生变化了,不再是偏向(独占)锁了,锁会升级为轻量级锁,两个线程公平竞争,哪个线程先占有锁对象并执行代码,锁对象的Mark Word就执行哪个线程的栈帧中的锁记录。
  2. 重量级锁:如果竞争的这个锁对象的线程更多,导致了更多的切换和等待,JVM会把该锁对象的锁升级为重量级锁,这个就叫做同步锁,这个锁对象Mark Word再次发生变化,会指向一个监视器对象,这个监视器对象用集合的形式,来登记和管理排队的线程。

了解了锁升级的过程,我们就看看这个重量级锁是个什么东西。前面说了,重量级锁时Mark Word中会有一部分空间记录的是一个Monitor对象的地址。当对象的锁级别升级为“重量级锁”时,JVM就开始采用Object Monitor机制控制各线程抢占对象的过程了。实际上这是JVM对操作系统级别Mutex Lock(互斥锁)的管理过程。

Monitor

什么是Monitor?

Monitor的类名叫做ObjectMonitor,源码参考:ObjectMonitor源码

  1. Monitor是一种用来实现同步的工具,他是在Jvm中实现的,用C++编写的。
  2. 与每个java对象相关联,即每个java对象都有一个Monitor与之对应。
  1. Monitor是实现Sychronized(内置锁)的基础。

Monitor的基本结构是什么?

  1. _owner字段:初始时为NULL表示当前没有任何线程拥有该monitor record,每当线程成功拥有该锁后Owner保存当前线程唯一标识,当锁被释放时又设置为NULL。
  2. _WaitSet:存放处于wait状态的线程队列。
  1. _EntryList:存放处于等待锁block状态的线程队列。
  2. _recursions:锁的重入次数。
  1. _count:用来记录该线程获取锁的次数。

ObjectMonitor的工作过程

ObjectMonitor中通过三个集合来存储不同状态的线程:

  • EntrySet:停留在这个区域的线程由于还没有获得对象操作权限的原因,依然停留在synchronized同步块以外,具体来说就是位于synchronized(Object)这句代码的位置,还没有进入到synchronized同步块内。处于“Entry Set”区域的线程,其线程状态被标识为BLOCKED。
  • Owner:对象操作权持有区,一个时间点最多有一个线程处于这个区域。也就是说一个时间点只可能有一个线程能拥有这个对象的操作权限。简单点就是当前在synchronized同步块内正在执行的线程在这个集合中。
  • WaitSet:某一个线程通过wait等相关方法释放了对象的操作权限,但是只要这个线程没有退出synchronized同步块,就不会释放这个对象的抢占权。这是因为没有退出synchronized同步块,且暂时没有对象操作权限的线程都会被放置到待授权区域(Wait Set)。但是并不是处于待授权区(Wait Set)的线程都可以重新参与对象操作权的抢占,而是只有通过notify()或者notifyAll()相似方法被通知转移的线程能够参与。注意,每个对象的Object Monitor控制过程相对独立,但是一个线程可以同时拥有一个或者多个对象的操作权限。

标签:Word,一篇,Synchronized,对象,lock,就够,synchronized,线程,指针
来源: https://blog.csdn.net/qq_34035160/article/details/121705958

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有