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

2023-11-30 23:20

本文主要是介绍【JUC】十八、happens-before先行发生原则,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1、先行发生原则happens-before
  • 2、happens-before总原则
  • 3、8条happens-before规则
  • 4、案例

1、先行发生原则happens-before

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

  • happens-before体现的是对可见性和有序性的约束。
  • happens-before是并发环境下,两个操作是否可能存在冲突的判断依据

在这里插入图片描述

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

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行后:

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

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

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

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

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

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

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

4、案例

private int value = 0;private int getValue(){return value;
}private int setValue(){return ++value;
}

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

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

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

怎么修复?

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

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

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

private volatile int value = 0;private int getValue(){return value;  //利用volatile保证读取操作的可见性
}private synchronized int setValue(){  //利用synchronized保证复合操作的原子性return ++value; 
}

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

这篇关于【JUC】十八、happens-before先行发生原则的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/438918

相关文章

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

当你输入一个网址后都发生什么

原文:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/  作为一个软件开发者,你一定会对网络应用如何工作有一个完整的层次化的认知,同样这里也包括这些应用所用到的技术:像浏览器,HTTP,HTML,网络服务器,需求处理等等。 本文将更深入的研究当你输入一个网址的时候,后台到底发生了一件件什么样的事~

【编程底层思考】详解Java的JUC多线程并发编程底层组件AQS的作用及原理

Java中的AbstractQueuedSynchronizer(简称AQS)是位于java.util.concurrent.locks包中的一个核心组件,用于构建锁和其他同步器。AQS为实现依赖于FIFO(先进先出)等待队列的阻塞锁和相关同步器提供了一套高效、可扩展的框架。 一、AQS的作用 统一同步状态管理:AQS提供了一个int类型的成员变量state,用于表示同步状态。子类可以根据自己

日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个 嫌疑犯的一个。以下为4个嫌疑犯的供词。

日本某地发生了一件谋杀案,警察通过排查确定杀人凶手必为4个 嫌疑犯的一个。以下为4个嫌疑犯的供词。 A说:不是我。 B说:是C。 C说:是D。 D说:C在胡说 已知3个人说了真话,1个人说的是假话。 现在请根据这些信息,写一个程序来确定到底谁是凶手。  static void Main()         {             int killer = 0;             fo

水处理过滤器运行特性及选择原则浅谈

过滤属于流体的净化过程中不可缺的处理环节,主要用于去除流体中的颗粒物或其他悬浮物。水处理过滤器的原理是利用有孔介质,从流体中去除污染物,使流体达到所需的洁净度水平。         水处理过滤器的滤壁是有一定厚度的,也就是说过滤器材具有深度,以“弯曲通 道”的形式对去除污染物起到了辅助作用。过滤器是除去液体中少量固体颗粒的设备,当流体进入置有一定规格滤网的滤筒后,其杂质被阻挡,而

一个瑞典游戏工作室决定离开索尼,之前和之后都发生了什么?

我们在前两篇中探究了国家政策、硬件基础与黑客文化如何让瑞典成为了游戏热土,而它充满地域特色的开发者社区与教育体系的构建,又是如何聚拢了游戏人才,让体系持续生长扩张。 除了大学、科技园和开发者之家外,我们此行从斯德哥尔摩到舍夫德到马尔默,还采访了三家知名工作室的创始人。它们一家产出如今罕见的双人合作游戏,还有一位特立独行的作者型开发者屡屡占据头条;一家贡献了现象级网红作品,当前在朝“正经向”大

关于Qt在子线程中使用通讯时发生无法接收数据的情况

在多线程应用中,串口通讯或TCP通讯的场景常常涉及到持续的读写操作,如果子线程处理不当,可能会导致信号阻塞问题。本文将通过串口通讯或TCP通讯为例,详细解释如何在多线程环境中避免信号阻塞,并提供代码示例。 1. 问题背景 假设我们在一个应用程序中使用多线程处理串口或TCP通讯,通常会在子线程中实现持续的数据读取。为了确保实时处理数据,常见的做法是在子线程的 run() 方法中使用 while

微分先行PID控制算法

微分先行PID控制算法 微分先行PID控制算法: 注:本文内容摘自《先进PID控制MATLAB仿真(第4版)》刘金琨 编著,研读此书受益匪浅,感谢作者! 微分先行PID控制算法: 微分先行PID控制结构,其特点是只对输出量 y ( k ) y(k) y(k)进行微分,而对给定值 y d ( k k ) y_d(kk) yd​(kk)不做微分。这样,在改变给定值时,输出不会改变

JAVA并发编程JUC包之CAS原理

在JDK 1.5之后,java api中提供了java.util.concurrent包,简称JUC包。这个包定义了很多我们非常熟悉的工具类,比如原子类AtomicXX,线程池executors、信号量semaphore、阻塞队列、同步器等。日常并发编程要用的熟面孔基本都在这里。        首先,Atomic包,原子操作类,提供了用法简单、性能高效、最重要是线程安全的更新一个变量。支持

Flink实例(十八):Flink 异步IO (三)实例 (一)

生成6条数据,从0开始递增的6个数字。模拟异步查询之后,加上时间戳输出 public class AsyncIODemo {public static void main(String[] args) throws Exception {StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnviro