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++中)

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

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

相关推荐
a程序小傲9 分钟前
蚂蚁Java面试被问:向量数据库的相似度搜索和索引构建
开发语言·后端·python·架构·flask·fastapi
骇客野人10 分钟前
maven生命周期构建和测试发布项目
java·maven
w***765515 分钟前
JS vs jQuery:核心差异解析
开发语言·javascript·jquery
黎雁·泠崖21 分钟前
Java面向对象:购物车系统完整版+全系列考点复盘
java·开发语言
初次见面我叫泰隆27 分钟前
Qt——2、信号和槽
开发语言·c++·qt
一颗青果1 小时前
C++的锁 | RAII管理锁 | 死锁避免
java·开发语言·c++
一分之二~1 小时前
回溯算法--解数独
开发语言·数据结构·c++·算法·leetcode
Gofarlic_oms11 小时前
跨国企业Cadence许可证全球统一管理方案
java·大数据·网络·人工智能·汽车
Smilecoc1 小时前
ChromeDriverManager:自动下载和管理chromedriver版本
开发语言·python
天燹1 小时前
Qt 6 嵌入 Android 原生应用完整教程
android·开发语言·qt