Synchronized学习总结

本文详细介绍了Java中的Synchronized用法,包括修饰实例方法、静态方法和代码块。深入探讨了其底层语义,指出无论是哪种形式,最终都涉及对对象的加锁。此外,解析了Java对象头与monitor的关系,分析了Synchronized的优化策略,如偏向锁、轻量级锁和重量级锁,并讨论了它们之间的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Synchronized 学习总结

1.用法

A.synchronized 修饰普通的方法,此时锁的是当前实例的对象

B.修饰静态方法

public static synchronized methodC() {}//对类加锁,即对所有此类的对象加锁

C.synchronized修饰代码块: 锁定的是括号内的对象

public void synMethod0() {

synchronized (this){

//do something

}

}

2.底层语义

synchronized 修饰实例方法 其本质与修饰this对象,修饰静态方法相当于修饰this.class ,修饰代码块 形式为ynchronized(Object)

。归根结底 ,synchronized相关的代码都是synchronized(obj)这样的格式,即对对象加锁

同步代码块是通过 monitorentor monitorexit 来实现的,而对方法 是在 方法标识中加入 ACCSYNCHORNIZED

monitorenter:每个对象都有一个监视器锁(monitor),当monitor被占用时就处于锁定状态,线程在执行monitorenter时,会尝试获取monitor的所有权过程如下:如果该monitor的进入者为0 ,这该线程进入monitor,将进入数设置为1 ,该线程即为该monitor的拥有者。如果该monitor已经被其他线程占有,则进入数++,本线程进入阻塞状态,直到该monitor的进入数为0,再重新尝试获取monitor的拥有权。

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

ACCSYNCHORNIZED:当方法被调用执行的时候,会检查方法的ACCSYNCHORNIZED访问标志是否被设置了,如果设置了,线程在执行该方法的时候,会先去获取monitor锁,获取完成之后才会执行方法,在方法完成之后,会释放monitor锁。

synchronized的几种使用方式的本质都是一样的,在语义底层都是通过monitorenter、monitorexit指令实现对monitor对象的获取和释放。

3.java对象头与monitor

java对象头:在jvm中,对象在内存中的布局分为三块,对象头,实例变量,填充数据

实例变量存放类的属性数据信息。填充数据:用于保证对象大小为8字节的整数倍。

对象头

如果是普通对象使用两个字宽存储对象头,如果对象是数组,则使用三个字宽 ,在32位虚拟机中一字宽为4字节,64位一字宽为8字节。

对象头的组成:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vbNjUmwm-1616297989736)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210317171059008.png)]

对象运行期间,Markword中的存储结构也会发生变化

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UD3JNV9E-1616297989738)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210317172649764.png)]

其中,重量级锁状态时:指向互斥量(重量级锁)的指针 指向的就是monitor对象的起始地址。

monitor对象 —针对于重量级锁

在不同的锁状态下,Mark word会存储不同的信息,当锁为重量级锁(所得标志位为 10) 时,markword中会指向Monitor对象的指针,这个monitor对象称为管程或者监视器锁,每个对象都存在着一个monitor对象与之关联。

java虚拟机HotSpot中,monitor对象是由ObjectMonitor 构成的

monitor中包含有部分重要变量:

a.owner:指向一个持有当前monitor的ObjectWaiter对象(每一个等待锁的线程都会被封装成为一个ObjectWaiter对象)

b._cxq:存储ObjectWaiter对象的单向列表,在多个线程竞争锁的时候,线程们会先进入这个列表

c._EntryList:存储位于Blocked状态的ObjectWaiter对象列表

d._waitSet:存储处于wait状态ObjectWaiter列表 调用了java中的wait()方法会被放入其中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0QUck9G-1616297989739)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210317193115477.png)]

线程锁的获取就是将owner指向自己

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzFQSltl-1616297989741)(C:\研-作业报告代码\Java多线程学习归纳\watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mjc2MjEzMw==,size_16,color_FFFFFF,t_70)]

多个线程在竞争monitor锁时,会先进入_cxq列表(所有请求锁的线程都会放在则个List里面)

有资格成为候选资源的线程会进入EntryList。

在任何时候,最多只有一个线程正在竞争锁 该线程为OneDeck

在获取到锁之后 就会将owner赋值为自己

如果当前线程调用了wait方法,会失去monitor锁,进入waitSet列表

JVM每次会从cxq的队尾中取出一个数据,作为线程竞争者,但是在并发情况下 ,cxq会被大量进程进行CAS访问,为了降低竞争,JVM将部分进程移入到EntryList中作为候选进程,并指定EntryList中的某个线程为OneDeck线程(一般为最先进去的),owner线程一般不会直接将锁传递给oneDeck ,而是给他一个竞争的机会,也称为(竞争切换

oneDeck线程获取到了锁资源之后,会变为Owner线程,如果线程执行了wait方法阻塞,会失去锁,进入waitSet ,然后再接收到notify或者Notufyall 之后会进入EntryLsit中 进入下一趟循环。

Synchronized是非公平的锁,线程在进入cxq之前会自旋试图获取锁,获取不到才会进入到cxq中,这对队列中到的对象不公平

Synchronized的优化

在java1.6中,实现了对Synchronized的优化,引入了偏向锁,轻量锁

偏向锁

在大多数情况,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,偏向锁是为了让获得锁的代价更低。

当锁对象第一次被线程获取的时候,虚拟机把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中的偏向线程ID,并将是否偏向锁的状态位置置为1。

以后该线程进入这个同步块时就不用进行CAS操作来加锁,去锁,只用测试以下对象头中的markword 中是否存储着指向当前线程的偏向锁。如果测试成功,说明该线程已经获得了锁,虚拟机就可以不再进行任何同步操作

如果没有,还有判断当前MarkWord 中 偏向锁的标志是否为1 (为1 代表 当前是偏向锁),如果没有设置,则使用CAS竞争锁,设置了的话,则尝试CAS将对象头的偏向锁的指向当前线程。

偏向锁使用了一种 竞争才会释放锁的机制

轻量级锁

在锁对象为偏向锁的情况下 产生竞争,这个时候会升级成为轻量级锁(自旋锁,无锁)。自旋的原因时为了一直去争夺锁对象

每个要获取该锁对象的线程都会在自己的栈帧中生成一个LockRecord 对象 ,此时锁对象的对象头中的markword 前面部分是一个lockReocrd对象的地址,线程们争夺锁就是为了将自己的LockRecord 对象的地址 赋值给 锁对象 。这个赋值过程是一个CAS 操作 (从锁中读取LR地址,修改为自己的LR,写回 查看之前LR地址有无被修改)并且一直自旋进行。

重量级锁

在轻量锁的情况下,如果竞争激烈 会升级为重量级锁。在jdk1.6之前,如果有线程自旋超过了10次,或者在自旋的线程数目超过了cpu核数的一半会升级为中级锁,在jdk1.6之后,出现了自适应自旋 Adpative spinning ,由jvm自己控制锁的升级。

重量级锁与轻量级锁的区别

操作系统中存在两种状态:用户态,核心态 一些指令只能在核心态下才能进行,用户态下不可进行,因此会需要执行系统调用,让自己进入核心态,然后执行。

在java中,使用 Sychronized 重量锁(monitor)时,需要系统分配锁对象(monitor),需要进入到核心态,因此开销大

为什么要从轻量级锁升级到重量级锁:

在轻量级锁中,所有的线程都在自旋争夺锁,这是建立在线程争夺并不严重,且每个线程占用锁的时间不会太长这一情况下的,所以在jdk1.6之前,要求线程的自旋次数不超过10次,如果竞争过大,且有线程占锁时间很长,cpu就被消耗了。

什么要从轻量级锁升级到重量级锁:

在轻量级锁中,所有的线程都在自旋争夺锁,这是建立在线程争夺并不严重,且每个线程占用锁的时间不会太长这一情况下的,所以在jdk1.6之前,要求线程的自旋次数不超过10次,如果竞争过大,且有线程占锁时间很长,cpu就被消耗了。

与轻量级锁相对比,重量锁需要切换状态,进入核心态。但是所有需要该锁但是没有获取到的线程会进入Bolcked(阻塞)状态(获取锁的线程执行wait() 或者sleep()方法之后 会进入waiting状态),不会消耗cpu,只有获取到锁的线程才会消耗cpu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值