JUC三大底层组件

推荐

《深入学习Java虚拟机》-Synchronized讲得很好
《并发编程的艺术》-主要来源
小刘思源码 CAS篇 https://www.bilibili.com/video/BV1kE411u7bj?p=3

并发产生数据误读的原因

1 指令顺序优化,这一点我们往往通过原子语句避免
2 多执行器缓存不一致
3 非原语执行时线程切换

实现线程安全的三大组件

CAS,Synchronized,volatile

CAS

使用

通过actomic包下使用各种不同类型的CAS方法
我们可以通过以下形式来使用CAS

while(compareAndSet(0,1));

它会一直循环直到成功

原理

CAS一条指令CMPXCHG实现原子性交换,不会因为调换顺序而脏读,使用时强行将缓存读会内存,并将其它缓存置为无效

问题与解决

ABA问题:版本号解决
CAS一直处于就绪或运行状态,空占CPU:靠JVM支持pause指令解决,pause指令可以延迟流水线指令,避免预测的指令占满流水线而在结束时不得不清空

volatile

volatile是如何实现锁的功能

volatile内存语义可以使用它的happens-before规则解读,那就是volatile的读一定可见于后序对volatile的读操作,而线程先序操作一定要可见于后续操作,所以volatile写的线程之前的一切操作一定可见于使用volatile读的线程

Store内存屏障有强制写入主内存和禁止相关重排的作用
Load内存屏障有强制从主内存读和禁止相关重排的作用
所以机器在检测到volatile写时,会在前面增加store-store屏障,防止应该可见于volatile读的内存变量修改被重排序到后面,对于同一个线程的读操作我们没有必要加上屏障,因为它们使用的是相同的本地内存,所以不存在load屏障,emm然而真相是在后面增加了store-load屏障,这一点笔者将后续关注。
volatile读时,我们要在volatile读后采用load-load,load-store屏障,load-load是为了保证后续读操作都可以读取刷新了的新本地内存,而load-store的作用是为了防止写入操作被提前,然后被覆盖

Synchronized

使用

Synchronized常用方式是加在方法,代码块上。加在普通方法上相当于对这个对象上锁,加在静态方法上说明对类上锁,加在代码块上是为了获得一个对象的局部锁

class test{
	//其它线程在Synchronized资源被占用的情况下是无法进入的
	public synchronized void find1(){
		if(time%2==1){
			wait();
		}
		notifyAll();
	}
	public synchronized static void find2(){
		sleep(100);
	}
	Object object = new Object();
	public void find3(){
		synchronized(object){
			notifyAll();
		}
	}
}

原理

每一个阶段Synchronized都会先尝试直接获取锁,所以它是非公平锁。线程每一次进入都会判断持有线程是否是自己,如果是,直接进入,所以是可重入锁

百度百科
一个对象作为偏向锁或轻量级锁就不可以再被hash计算了,因为31位会被hashcode占据了,如果要作为锁,或者已经作为锁,却要hashcode(),就会直接膨胀成重量级锁,重量级锁拥有着指向ObjectMonitor的指针,ObjectMonitor中有额外的空间来存放这个对象在001非锁状态下的MarkWord。
可能有人疑问:轻量级锁不可以再进行Hash计算,那可以Hash计算之后再轻量级锁吗,因为轻量级锁也有ReplacedMarkWord,可以存放hashcode。这一点要看偏向锁状态来论,因为偏向锁是可以通过JVM参数取消的。取消偏向锁之后应该是可以hash再轻量级锁的(这一点理论上没问题,但是要看JVM官方实现)。但如果不取消的话,进入轻量级锁需要经过偏向锁,偏向锁已经不允许了,那轻量级锁自然不可能。

当一个线程想要进入Synchronized区域中时,它就需要将对象头的偏向锁位设置为1,虚拟机会把对象头变为偏向锁型,然后会通过CAS把线程ID赋值到对象头,如果CAS不成功,说明已经有线程持有锁,那么它会检查这个线程ID是不是自己,如果不是,那么锁对象就会立即升级为轻量级锁,挂起原来持有锁的线程,让它也完成初始化LockRecord的工作,轻量级锁依然属于原来那个线程。无论是竞争撤销还是无竞争撤销都会把偏向锁位置0。

在这个阶段,每个尝试访问的线程栈里,都会创建一个LockRecord来存放这个偏向锁的markWord,LockRecord里面存放着Replaced MarkWord和owner,Replaced MarkWord是给持有锁的线程使用的,它在获取锁时会将对象的MarkWord记录在这,然后在释放时尝试放回去;而owner是这个锁对象头中MarkWord的指针,是为了让线程能够找到这个锁。具体的流程是:每个没有锁的对象会不断尝试把owner指针指向的锁对象头markWord改为指向自己LockRecord。

如果长期不成功(这个长期是会变的,因为synchronized是自适应自旋),锁对象会再次膨胀,markWord变为指向ObjectMonitor的指针,并更改标志位,所有无法获得互斥对象的线程陷入阻塞,与偏向锁不同的是,这一次不会挂起持有锁的线程,在持有锁的线程尝试撤销锁时,发现自己无法markword的替换,那么它不仅会释放锁,还会主动唤醒沉睡的线程。

重量级时,发现ObjectMonitor中count不为0并且owner不是自己,就会进入阻塞队列,这个阻塞队列是公平的。如果在锁对象期间被阻塞了,就算被唤醒也需要重新排队,每次被唤醒时会检查自己是不是队头,不是那么会重新阻塞,如果排到自己,是会从原来阻塞的地方继续向下执行的,这也是为什么需要写while(){wait;}的原因

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值