栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA的原子性,可见性,有序性与内存屏障

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA的原子性,可见性,有序性与内存屏障

JAVA的原子性,可见性,有序性 原子性

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。

只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。

java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
例如:

public class Test {
    public static int a=0;
 
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10000;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    a++;
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    a++;
                }
            }).start();
        }
        Thread.sleep(1000);
        System.out.println(a);
    }
}
可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

于可见性,Java提供了volatile关键字来保证可见性。

当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

public class Test01 {
    private static boolean run=true;
    private static void test(){
        System.out.println("start------");
        while (run){
 
        }
        System.out.println("end--------");
    }
 
    public static void main(String[] args) throws InterruptedException {
        new Thread(Test01::test).start();
        Thread.sleep(10000);
        run=false;
    }
}
有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

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

public class Test02 {
    public static int x=0,y=0;
    public  static int a=0,b=0;
 
    public static void main(String[] args) throws InterruptedException {
        for (long i=0;i 
内存屏障 

一、什么是JAVA内存屏障

内存屏障是为了解决因为cpu,高速缓存,主内存出现的时候,导致的可见性和重序性问题。

1、原理解释
因为计算机的运算任务需要CPU和内存相互配合共同完成,其中CPU负责逻辑计算,内存负责数据存储。但是在真正的实际开发中CPU是要与内存进行交互的,但因为内存和CPU的计算速度是有差距的,因此为了提高CPU的利用效率,现代处理器结构都加入了一层读写速度尽可能接近CPU运算速度的高速缓存来作为内存与CPU之间的缓冲:将运算需要使用的数据复制到缓存中,让CPU运算可以快速进行,计算结束后再将计算结果从缓存同步到主内存中,这样处理器就无须等待缓慢的内存读写了。但在高速缓存解决CPU和内存之间速度的矛盾,但是在多CPU系统中也带来了新的问题:可见性问题和重排序问题。

二、Java层面的内存屏障
内存屏障(Memory Barrier)与内存栅栏(Memory Fence)是同一个概念,不同的叫法。而Java的层面 是应用volatile关键字去修饰变量,解决了编译器层面的可见性与重排序问题。

1、Java的屏障类型
Java中有四种类型:LoadLoad Barriers、StoreStore Barriers、LoadStore Barriers、 StoreLoad Barriers。从该四个类型中我们可以看到他们都是有Load和Store的不同排序组成。而这两个指令是来自硬件的内容。

1.1、Load指令和Store指令
Load指令(读屏障):它将内存存储的数据拷贝到处理器的缓存中。

Store指令(写屏障):它主要实现让当前线程写入高速缓存中的最新数据更新写入到内存,让其他线程也可见。

1.2、LoadLoad Barriers
简单的理解就是当有两个Load,一个Load1一个Load2,Load1加载代码要从内存里面读取的数据读取完毕之后,Load2加载代码才能读取数据。

1.3、StoreStore Barriers
理解为当有两个Store,一个是Store1一个是Store2,Store1的写入操作已经把数据写入到内存里面,并且保证Store1的写入操作对其它处理器可见之后,才会对Store2存储代码进行写入操作执行。

1.4、LoadStore Barriers
理解为当有一个Load1和一个Store2,要先保证Load1加载代码要从内存里面读取的数据读取完毕之后,Store2存储代码才会进行写入操作。

1.5、StoreLoad Barriers
理解为当有一个Store1和Load2,要先保证Store1的写入操作已经把数据写入到内存里面,并且确认Store1的写入操作对其它处理器可见,Load2加载代码才从内存里面读取数据。而且因StoreLoad Barriers同时具备其他三个屏障的效果,因此也称之为全能屏障,是目前大多数处理器所支持的,但是相对其他屏障,该屏障的开销相对昂贵的。

2、内存屏障在Volatile关键字里面的作用
在每个volatile写操作前插入StoreStore屏障,这样就能让其他线程修改A变量后,把修改的值对当前线程可见,在写操作后插入StoreLoad屏障,这样就能让其他线程获取A变量的时候,能够获取到已经被当前线程修改的值

在每个volatile读操作前插入LoadLoad屏障,这样就能让当前线程获取A变量的时候,保证其他线程也都能获取到相同的值,这样所有的线程读取的数据就一样了,在读操作后插入LoadStore屏障;这样就能让当前线程在其他线程修改A变量的值之前,获取到主内存里面A变量的的值。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/704012.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号