IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 多线程原子性、可见性的验证 -> 正文阅读

[Java知识库]多线程原子性、可见性的验证

原子性

原子性的概念是 当一个线程访问某个共享的变量时,对其他线程来看,该操作要么执行完毕要么没有发生,不会看到中间值。所以原子性只存在于多线程共享成员变量中,单线程或者多线程个对局部变量的操作都可以理解为是原子性的。

java中八大基本类型中long、double类型修饰的变量是非原子性,除此之外,剩下的六个都是原子性的,下面写一个demo来进行验证。

代码验证

因为 double 和long修饰的变量分别占32和64位,所以,32位系统对long类型变量寻址最少2次,而64位系统只需要执行1次,因此它们的非原子性只有在jdk32位下才能进行验证。
1、综上所述,要想验证的话,首先下载jdk32位版本,下载地址:jdk32位下载 下载之后配置全局变量;配置之后进入命令提示窗口查看java版本,如下显示为32位且为client端(没有数字提示即为32位;server端默认为64位,无法切换到32位,所以要下载32位jdk)
在这里插入图片描述
(server端的demo如下:)
在这里插入图片描述

2、打开idea,配置idea的启动jdk,为下载32位jdk的路径
在这里插入图片描述
在这里插入图片描述
3、最后代码来验证long类型的非原子性
这里启动两个线程,分别对共享变量赋予0和-1,然后第三个线程main线程来进行查看

public class AtomicTest implements Runnable {

    static long value = 0;
    private final long valueToSet;

    public AtomicTest(long valueToSet) {
        this.valueToSet = valueToSet;
    }

    public static void main(String[] args) {

        Thread thread1 = new Thread(new AtomicTest(0L));
        Thread thread2 = new Thread(new AtomicTest(-1L));

        thread1.start();
        thread2.start();

        long snapShort;
        //java模式为client模式,不会进行循环优化,snapShort=value不会循环外提
        while (0 == (snapShort = value) || -1 == snapShort) {
        }
        //不等于0和1的时候打印出来
        System.out.printf("Unexpected data: %d(0x%016x)", snapShort, snapShort);
        //退出程序,否则子线程无限循环,程序永远不会终止
        System.exit(0);
    }

    @Override
    public void run() {
        //两个线程不断的给共享变量value进行赋值,如果
        for (; ; ) {
            value = valueToSet;
        }
    }
}

如下为打印的结果:
在这里插入图片描述
我们可以看到,这里产生了一个中间值,非0(0x0000000000000000)也非-1(0xffffffffffffffff)所以,可以证明,long类型修饰的变量在32位系统下是不会保证原子性的。

解决方法

解决方法就是对共享变量value加上volatile关键字了,这里有人会问,volatile不是不能保证原子性吗?
volatile是可以保证写操作的原子性的,因为大部分例子都是拿i++进行举例,i++是分了三步(read-modify-write),包括读、写、更改,所以voliate肯定不能保证i++的原子性,但是本例子只有一个写操作,故可以保证原子性,所以面试的时候不要再说volatile不能保证原子性啦。

可见性

可见性的概念是 多线程环境下,一个线程更改了共享变量的值,其他线程可以立刻读取到更新的结果,这样其他线程不会读取到旧的数据,保证程序的正常运行。

代码验证

现在写一个demo:主线程先休眠1s(java语言会规定父线程在启动子线程之前,对变量的更改对于子线程来说是可见的,所以父线程休眠1s,要先启动子线程),子线程读取共享变量,然后主线程再修改共享变量,最后发现,子线程一直在循环,也就是一直读取的都是之前的变量。

public class VisibilityDemo {
    private static boolean flag = true;
    public static void main(String[] args) {

        new Thread(()->{
            while (flag){

            }
        }).start();
        
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        flag = false;
    }
}

结果是子线程一直无法读取到主线程更新共享变量后的值,这就是没有保证线程之间的可见性
在这里插入图片描述

导致原因

在说解决方法之前,先来分析一下为什么没有保证可见性。
因为主线程休眠的时间内,只有一个线程在使用共享变量,这就导致JIT编译器认为真的只有一个线程对其访问,从而导致JIT为了避免重复读取主存中的变量,提高运行的效率,就把flag变量一直定义为true。
另一方面,可见性和计算机的存储系统有很大的关系:
1、程序中的变量可能会被分配到处理器中的寄存器中(Register)而不是主内存中进行存储,每一个线程如果运行在不同的处理器上,那他们无法读取对方处理器内寄存器中的值,所以就会导致变量不可见的现象。
2、另外,处理器对主存的访问并不是直接的,是通过高速缓存(Cache)进行读取的,而在高速缓存和处理器之间还有一个缓冲区叫写缓冲器(Store Buffer),所以该线程对共享变量的更改可能只写到了写缓冲器中,并没有到主存内,而每个处理器的写缓冲器又是隔离的,所以也无法看到共享变量的更新。(没有写到自己的高速缓存中)
3、即便该处理器的线程将变量写到高速缓存时,该处理器通知其他处理器的时候,其他处理器可能仅仅将该变量同步到自己的无效化队列(Invalidate Queue)中,没有更新到自己的高速缓存中。(没有写到对方的高速缓存中)

解决方法

虽然一个处理器无法读取另外一个处理器中的变量,但是处理器之间可以遵循缓存一致性协议(Cache Coherence Protocol)来解决该问题:该处理器可以读取其他部件(主内存、其他处理器的高速缓存)到自身处理器中高速缓存的过程叫做缓存同步。
所以,从写入的角度来看:当前处理器一定要把变量更改后的值更新到自己的高速缓存或者主存中,这个过程叫做冲刷处理器缓存;从读取的角度来看:如果其他处理器更新了共享变量,当前处理器一定要从主存或其他处理器的高速缓存中拉取变量到自己的高速缓存中,这个过程叫做刷新处理器缓存
解决方法就是对该变量加上volatile关键字,它的作用就是高速JIT编译器,该变量会被多个线程访问,不需要进行优化,并且会使cpu执行冲刷处理器缓存和刷新处理缓存的过程。
下面是自己总结的一张图可以作为参考:
在这里插入图片描述

注意:可见性问题是多线程情况下产生的,和运行在几个处理器上是没有关系的,即使多个线程运行在同一个处理器上时,因为有时间片的分配以及上下文切换(一个线程对变量的修改会被该线程的上下文保存起来,导致其他线程无法查看),还是无法保证可见性。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-08 18:52:54  更:2022-06-08 18:54:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:07:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码