• 2.2并发三大特性可见性、原子性、有序性


    2.并发三大特性可见性、原子性、有序性
    可见性:

    当一个线程修改了某个共享变量的值,其它线程应当能够立即看到修改后的值。Java使用共享内存模式来刷新内存方式来保证可见性 。

    可见性问题:

    volatile关键字保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中读取新值。synchronized和Lock也可以保证可见性,因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。

    有序性

    程序执行代码指令的顺序应当保证按照程序指定的顺序执行,即便是编译优化,也应当保证程序源语一致

    有序性问题:

    在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

    原子性:

    原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。
    在java中,对基本数据类型的变量的读取和赋值操作是原子性操作有点要注意的是,对于32位系统的来说,long类型数据和double类型数据(对于基本数据类型,byte,short.intfloatbooleanchan读写是原子操作),它们的读写并非原子性的,也就是说如果存在两条线程同时对long类型或者double类型的数据进行读写是存在相互干扰的,因为对于32位虚拟机来说,每次原子读写是32位的,而long和double则是64位的存储单元,这样会导致一个线程在写时,操作完前32位的原子操作后,轮到B线程读取时,恰好只读取到了后32位的数据,这样可能会读取到一个肝非原值又不是线程修改值的变量,它可能是“半个变量”的数值,即64位数据被两个线程分成了两次读取。但也不必太担心,因为读取到“半个变量”的情况比较少见,至少在目前的商用的虚拟机中,几乎都把64位的数据的读写操作作为原子操作来执行。

    Java内存模型:每个样都自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为happens-before原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
    指令重排序:java语言规范规定JVM线程内部维持顺序化语义。即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码顺序不一致,此过程叫指令的重排序。指令重排序的意义是什么?JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。
    下图为从源码到最终执行的指令序列示意图:

    在这里插入图片描述
    as-if-serial语义
    as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器,runtime和处理器都必须遵守as-if-serial语义。
    为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

    happens-before原则
    只靠sychronized和volatile关键字来保证原子性、可见性以及有序性,那么编写并发程序可能会显得十分麻烦,幸运的是,从JDK5开始,Java使用新的JSR-133内存模型,提供了happens-before原则来辅助保证程序执行的原子性、可见性以有序性的问题,它是判断数据是否存在竞争、线程是否安全的依据,happens-before 原则内容如下:

    1.程序顺序原则,即在一个线程内必须保证语义串行性,也就是说按照代码顺序执行。
    2.锁规则 解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,也就是说,如果对于一个锁解锁后,再加锁那么加锁的动作必须在解锁动作之后(同一个锁)。
    3.volatile规则volatile变量的写,先发生于读,这保证了volatile变量的可见性,简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻,不同的线程总是能够看到该变量的最新值。
    4.线程启动规则 线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start方法之前修改了共享变量的值,那么当线程B执行start方法时,线程A对共享变量的修改对线程B可见
    5.传递性A先干B B先于C那么A必然先于C
    6.线程终止规则线程的所有操作先于线程的终结,Threadjoin()方法的作用是等待当前执行的线程终止。假设在线程B终止之前,修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。
    7.线程中断规则 对线程interrupt)方法的调用先行发生干被中断线程的代码检测到中断事件的发生,可以通Thread.interrupted()方法检测线程是否中断。
    8.对象终结规则对象的构造函数执行,结束先于finalize()方法

    volatile内存语义

    volatile是Java虚拟机提供的轻量级的同步机制。volatile关键字有如下两个作用:

    1)保证被volatile修饰的共享变量对所有线程总数可见的,也就是当一个线程修改了一个被volatile修饰共享变量的值。新值总是可以被其他线程立即得.

    2)禁止指令重排序优化。

    volatile的可见性

    关于volatile的可见性作用,我们必须意识到被volatile修饰的变量对所有线程总数立即可见的,对volatile变量的所有写操作总是能立刻反应到其他线程中。

    线程安全,需要注意的是一旦使用synchronized修饰方法后,由于synchronized本身也具备与volatile相同的特性,即可见性。因此在这样种情况下就完全可以省去volatile修饰变量。

    volatile禁止重排优化

    volatile关键字另一个作用就是禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象,关于指令重排优化前面已详细分析过,这里主要简单说明一下volatile是如何实现禁止指令重排优化的

  • 相关阅读:
    2225. 找出输掉零场或一场比赛的玩家
    D课堂 | DDoS、CC,网站被攻击怎么办?
    nlp中如何数据增强
    leetcode148-Sort List
    文件操作和IO
    STM32F407-- DMA使用
    目标检测YOLO实战应用案例100讲-高速铁路供电安全检测监测系统图像智能识别
    光点科技数据口袋数据填报系统满足多类型企业报表需求_光点科技
    番茄小说推文和番茄短剧推广授权怎么申请
    自定义windows右键菜单,软件卸载后 右键菜单残留 打开方式残留 解决方法
  • 原文地址:https://blog.csdn.net/Mao_yafeng/article/details/127126505