syncronized使用与深入研究

syncronized使用与深入

synchronized使用

1、原子性、有序性、可见性

1.1原子性

数据库的事务:ACID

A、数据库中,一个事务的原子性指事务中的所有操作,要么全部完成,要么全部不完成,不会只完成一部分。 并发编程的原子性:一个或多个指令的CPU执行过程中不允许中断的。

问题,i++操作是原子性?

不是,i++操作包含3个指令: 1.读取i的值 2.将i的值加1 3.将i的值写回

javac CompanyTest.java

javap -v CompanyTest.class

getfield:从主内存拉取数据到cpu寄存器

iadd:在寄存器中对数据进行+1

putfield:讲cpu寄存器中的结果甩到主内存中

如何保证i++是原子性的。

锁:synchronized,lock,atomic(CAS)

synchronized锁:

lock锁:使用lock锁也有类似的概念,就是在操作i++的三个指令前,先基于 AQS成功修改state后才可以操作

使用synchronized和lock锁时:可能会触发将线程挂起的操作,而这种操作做会触发 内核态和用户态的切换,从而消耗资源。

CAS方式就相对synchronized和lock锁的效率更高(也说不定),因为cas就不会触发线程挂起操作。

CAS: compare and swap ,自旋锁

线程基于CAS修改数据的方式:先获取主内存数据,在修改之前,先 比较数据是否一直没变,如果一直没变修改主内存数据;如果不一致,放弃这次修改。

CAS就是比较和交换,而比较和交换是一个原子操作。JAVA层面无所,底层实际有锁。

CAS在java层面就是Unsafe类中的CAS类中提供的一个native方法,这个方法只提供了CAS成功返回true, 如果需要重试策略,自己实现。

cas问题:

1、cas只能对一个变量的修改实现原子性,无法保证对多个参数进行原子性操作。 2、CAS存在ABA问题。

1.1 a线程修改主内存数据从1-2,卡在1之后

1.2 b线程修改主内存从1-2,完成。

1.3 c线程修改主内存2-1,完成。

1.4 a线程执行cas操作,发现主内存1,没问题,直接修改。

1.5 解决方式:加版本号。确保值一致,版本号码一致 。

问题:写一个cas操作实例 todo

public static void main(String[] args) { AtomicStampedReference<Integer> atomicStamp=new AtomicStampedReference<>(0,0); int stamp=0; if(atomicStamp.compareAndSet(0,1,stamp,stamp+1)){ stamp=stamp+1; System.out.println("stamp:"+stamp); //stamp:1 System.out.println("atomicStamp:"+atomicStamp.getStamp()+"reference:"+atomicStamp.getReference()); //atomicStamp:1reference:1 } }

3、在CAS执行次数过多,但是一九无法实现对数据的修改,CPU会一直调度这个线程,造成对CPU的性能损耗。 CAS:在多核情况下,有lock指令保证只有一个线程在执行当前CAS。

3.1 synchronized的实现方式:CAS自旋一定次数后(可设置次数),如果还不成,挂起线程。

3.2 LongAdder的方式:当CAS失败后,将操作的值,存储起来,后续一起添加。

复制代码

while(true){ u.cas("xx",12312,1,2){ break; } }

1.2有序性

指令在CPU调度执行时,CPU会为了提升执行效率,在不影响结果的前提下,对CPU指令进行 重新排序。如果不希望CPU对指令进行重排序,怎么办?

可以对属性追加volatile修饰,就不会对当前属性的操作进行指令重排序。

什么时候指令重排:满足happens-before原则,即可重排序

单例模式-DCL双重判断。对象有执行,但是未初始化,导致返回报异常

申请内存,初始化,关联是正常顺序,如果CPU对指令重排,可能会造成: 申请内存,关联,初始化,在还没有初始化时,其他线程来获取数据,导致 获取到的数据虽然有地址引用,但是数据未初始化,都是默认值,使用时与预期不符。

1.3可见性

可见性:前面说过CPU在处理时,需要将主内存数据甩到我的寄存器中再执行指令,执行完指令后, 需要将寄存器数据再扔回到主内存中。倒是寄存器数据内存到主内存中是遵循MESI协议的, 不是每次操作结束就将CPU缓存数据同步到主内存,造成多个线程看到的数据不一致。

volatile每次操作后,立即同步数据到主内存。

synchronized:触发同步数据到主内存。

final,也可以解决可见性问题,final不可修改。

2、synchronized使用(对象头)

synchronized方法

synchronized代码块

类锁和对象锁:

对象锁-锁定对象,this 类锁-基础当前类的class加锁

synchronized锁是基于对象实现的!

synchronized是互斥锁,每个线程获取synchronized锁时, 基于synchronized绑定的对象去获取锁。

对象组成:对象头、实例数据、补齐填充 对象头:markword、class point(.class) 实例数据: 补齐填充:占用大小 markword组成:

导入依赖:

初始化的对象是无锁状态:

3、synchronized锁升级

synchronized在jdk1.6之前,一直是重量级锁:只要线程获取锁资源失败, 直接挂起(用户-内核态切换);在1.6之前synchronized效率贼低,再加上 Dong lea推出了ReentrantLock,效率比synchronized高很多。导致JDK团队 不得不在jdk1.6将synchronized做优化。

锁升级:

1、无锁状态、匿名偏向锁 :没有线程拿锁

2、偏向锁状态:没有线程的竞争,只有一个线程再获取锁资源

线程竞争锁资源时,发现当前synchronized没有线程占用锁资源,并且锁是偏向锁,使用CAS的方式, 设置o的线程ID为当前线程,获取到锁资源,下次当前线程再次获取时,只需要判断是偏向锁,并且 线程ID是当前线程ID即可,直接获得到锁资源。

3、轻量级锁:偏向锁出现竞争时,会升级到轻量级锁(触发偏向锁撤销,偏向锁撤销比较消耗资源) 轻量级锁的状态下,线程会基于CAS的方式,尝试获取锁资源,CAS的次数是基于自适应自旋锁实现的, JVM会自动的基于上一次获取锁是否成功,来决定这次获取锁资源要CAS多少次。

4、重量级锁:轻量级锁CAS一段次数后,没有拿到锁资源,升级为重量级锁。 重量级锁就是线程拿不到锁,就挂起。 偏向锁是延迟开始的,并且在开启偏向锁之后,默认不存在无锁状态,只存在匿名偏向。

编译器优化的结果,出现下列结果:

锁消除:线程在执行一段synchronized代码块时,发现没有共享数据的操作, 自动把加锁去掉。

锁膨胀:在一个多次循环的操作中频繁的获取和释放锁资源,synchronized在编译时看可能会优化到循环外部 for{ sync(o) }

4、synchronized-ObjectMonitor

涉及到ObjectMoitor一般是到达了重量级锁才会涉及。 在到达重量级锁之后,重量级锁的指针会指向ObjectMoitor对象(c++中)

问题:偏向锁会降级到无锁状态?

不会:锁升级只有单向没有反向,无锁->偏向锁->轻量级锁->重量级锁

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔7 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说12 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json