【JUC】十八、happens-before先行发生原则

文章目录

1、先行发生原则happens-before

在Java中,Happends-Before本质上是规定了一种可见性, A Happends-Before B,则A发生过的事情对B来说是可见的,不论A事件和B事件是否发生在同一个线程里。

  • happens-before体现的是对可见性和有序性的约束。
  • happens-before是并发环境下,两个操作是否可能存在冲突的判断依据
java 复制代码
y一定等于5?

如果线程A的操作(x=5)先行发生于线程B的操作(y=x),或者说这两个事件存在先行发生原则,那y=5一定成立,反之则不一定,因为x=5的改变可能还没从A线程的工作内存刷回主内存,线程就暂时挂起了。

但如果Java内存模型中的有序性都得靠volatile和synchronized来实现,就非常繁琐,而且日常开发也没见处处加这些关键字,这是因为谁先谁后在先行发生原则里已经立好了规矩。

2、happens-before总原则

1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2) 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行,如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法

举例:

值日表里写了周一张三,周二李四,但现在张三周一临时有事,和李四换班后,教室还是能打扫干净

3、8条happens-before规则

1> 次序规则 :一个线程内,按照代码顺序,写在前面的操作先于写在后面的操作

直白讲就是:同一个线程,前面一个操作把变量x赋值为1,那后面一个操作肯定知道x已经变成1了

2> 锁定规则:一个unLock操作先行发生于后面(这里的"后面"是指时间上的先后)对同一个锁的lock操作

直白说就是:一定是:A线程unlock后,B线程才能对同一个锁lock

java 复制代码
Lock lock = new ReentrantLock();

lock.lock();
try{

}finally{
	//先
	lock.unlock();
}
//后
lock.lock();
try{

}finally{
	lock.unlock();
}

3> volatile变量规则 :对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的"后面"同样是指时间上的先后
4> 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
5> 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作

执行顺序一定是第4行先,第2行后:

java 复制代码
Thread t1 = new Thread(() -> {
		System.out.println("QWE");   //后
	},"t1");
t1.start();  //先

6> 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。

直白说:检测到中断事件发生(检测到中断标志位变了),是先发生了interrupt方法的调用。一定是先发烧了,温度计才能检测到体温变了。

7> 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过isAlive()等手段检测线程是否已经终止执行

直白说:线程中的操作(比如run方法体)先全部执行完,线程才终止

8> 对象终结规则:一个对象的初始化完成(构造函数执行结束) 先行发生于它的finalize()方法的开始

翻译:肯定是先new了一个对象,才能垃圾回收这个对象

4、案例

java 复制代码
private int value = 0;

private int getValue(){
	return value;
}

private int setValue(){
	return ++value;
}

现在有线程A和线程B,线程A(在时间上先)调用了setValue方法,然后线程B调用同一对象的getValue方法,那线程B的返回值是?

java 复制代码
对照上面的8条规则:
  • 两个方法在不同线程,第一条规则用不上
  • 两个方法都未加锁,规则2也pass
  • 共享变量value没有加volatile,规则3pass
  • 先行发生规则不等价于时间上的先,这里目前直接没有已知的先行发生规则,无从传递,pass
  • ...

⇒ 无法通过happens-before原则推导出线程A happens-before线程B,虽然可以确认在时间上线程A优先于线程B,无法确认线程B获得的结果是什么,所以这段代码不是线程安全的。

java 复制代码
怎么修复?

方式一:加synchronized,如下,这样性能损失太大

java 复制代码
private int value = 0;

private synchronized int getValue(){
	return value;
}

private synchronized int setValue(){
	return ++value;
}

方式二:把value定义为volatile变量,由于setter方法对value的修改不依赖value的原值,满足volatile关键字使用场景

java 复制代码
private volatile int value = 0;

private int getValue(){
	return value;  //利用volatile保证读取操作的可见性
}

private synchronized int setValue(){  //利用synchronized保证复合操作的原子性
	return ++value; 
}

利用volatile保证读取操作的可见性,利用synchronized保证复合操作的原子性,结合使用锁和volatile 变量来减少同步的开销

相关推荐
爱上语文19 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people23 分钟前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
qmx_071 小时前
HTB-Jerry(tomcat war文件、msfvenom)
java·web安全·网络安全·tomcat
为风而战1 小时前
IIS+Ngnix+Tomcat 部署网站 用IIS实现反向代理
java·tomcat
技术无疆3 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
架构文摘JGWZ6 小时前
Java 23 的12 个新特性!!
java·开发语言·学习
拾光师7 小时前
spring获取当前request
java·后端·spring
aPurpleBerry7 小时前
neo4j安装启动教程+对应的jdk配置
java·neo4j
我是苏苏7 小时前
Web开发:ABP框架2——入门级别的增删改查Demo
java·开发语言
xujinwei_gingko7 小时前
Spring IOC容器Bean对象管理-Java Config方式
java·spring