并发编程系列之变量可见性问题探究

举报
yd_273762914 发表于 2021/11/18 22:12:07 2021/11/18
【摘要】 并发编程系列之变量可见性问题探究 1、什么是并发中的变量可见性问题 以例子的形式看看,定义一个变量,先用static修饰,在主线程修改之后,看看在新开的子线程里能被看到? public class ...

并发编程系列之变量可见性问题探究

1、什么是并发中的变量可见性问题

以例子的形式看看,定义一个变量,先用static修饰,在主线程修改之后,看看在新开的子线程里能被看到?

public class Example {
private static boolean flag = true;
    
    public void testss() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (IfTest.flag) {
                        i++;
                }
                System.out.println(i);
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        IfTest.flag = false;
        System.out.println("设置flag");
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

执行,控制台打印:

设置flag

ps:主线程对flag变量进行修改,子线程是不能看到的,所以里面一直在循环,不能打印统计数据值。然后怎么才能让并发线程看见?

  • 方式1:使用volatile关键字
public class Example {
private static volatile boolean flag = true;
    
    public void testss() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (IfTest.flag) {
                        i++;
                }
                System.out.println(i);
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        IfTest.flag = false;
        System.out.println("设置flag");
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

控制台打印:

设置flag
72071943

  • 方式2:使用synchronized同步锁
public class Example {
private static boolean flag = true;
    
    public void testss() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (IfTest.flag) {
                    synchronized (this) {
                        i++;
                    }
                }
                System.out.println(i);
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        IfTest.flag = false;
        System.out.println("设置flag");
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

控制台打印:

设置flag
86726163

2、什么是Java内存模型?

解答这个问题,需要涉及到Java的内存模型,如下所示,Java内存模型及操作规范:

  1. 共享变量都是放在主内存中的
  2. 每个线程都有自己的工作内存,线程只可操作自己的工作内存
  3. 线程要操作共享变量,需要从主内存中读取到工作内存,改变值之后要从工作内存同步到主内存

在这里插入图片描述

  • Java内存模型的同步交换协议,规定了8种原子操作

原子操作:不可被中断的一个或一系列操作

  1. lock(锁定):将主内存中的变量锁定,为一个线程所独占
  2. unlock(解锁):将lock加的锁解除,其他的线程有机会访问此变量
  3. read(读取):作用于主内存变量,将主内存中的变量值读取到工作内存
  4. load(加载):作用于工作内存,将read读取到的值保存到工作内存中的变量副本
  5. use(使用):作用于工作内存变量,将值传递给线程的代码执行引擎
  6. assign(赋值):作用于工作内存变量,将执行引擎处理返回的值重新赋值给变量副本
  7. store(存储):作用于工作内存变量,将变量副本的值传送到主内存中
  8. write(写入):作用于主内存变量,将store传送过来的值写入到主内存的共享变量中
  • Java内存模型的同步交互协议,执行上述8种原子操作时必须满足如下规则
  1. 不允许read和load,store和write操作之一单独出现。即不允许加载或同步工作到一半。
  2. 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变之后,必须将数据同步回主内存
  3. 不允许一个线程无原因地(无assign操作)将数据从工作内存同步到主内存中。
  4. 一个新的变量可能在主内存中诞生。
  5. 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次lock之后必须要执行相同次数unlock操作,变量才会解锁
  6. 如果对一个对象进行lock操作,那么会清空工作内存变量中的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始变量的值
  7. 如果一个对象事先没有被lock,就不允许对其进行unlock操作,也不允许去unlock一个被其他线程锁住的变量。
  8. 对一个变量执行unlock操作之前,必须将此变量同步回主内存中(执行store、write)
  • Java内存模型的同步协议,操作规范
  1. 将一个变量从主内存复制到工作内存要顺序执行read、load操作;要将变量从工作内存同步回主内存要用store、write操作。只要求顺序执行,不一定是连续执行

图引用网上资料:
在这里插入图片描述

3、保证变量可见性的方法

  1. final变量
  2. synchronized
  3. volatile修饰

4、Synchronized怎么做到可见性

  • synchronized语义规范:
  1. 进入同步块前,先清空工作内存中的共享变量,从主内存加载
  2. 解锁前,必须将修改的共享变量同步回主内存
  • synchronized是如何做到线程安全的?
  1. 锁机制保护共享资源,只有获得锁的线程才能操作共享资源
  2. synchronized语义规范保证了修改共享资源后,会同步回主内存,就做到了线程安全

5、volatile关键字解密

  • volatile语义规范:
  1. 使用volatile变量时,必须重新从主内存加载到工作内存,并且read、load是连续的
  2. 修改volatile变量后,必须马上同步回主内存,并且store、write是连续的
  • volatile可以做到线程安全?
    不能,因为volatile没有锁机制,线程是可以并发操作共享资源的

  • volatile相对synchronized有什么优点?

  1. 使用volatile比synchronized简单
  2. volatile性能比synchronized好
  • volatile的使用场景?
  1. volatile只能修饰成员变量
  2. 在多线程并发的场景才使用
  • volatile支持并发编程三大特效?
    并发编程三大特效:原子性、有序性、可见性。
  1. 可见性:volatile和synchronized关键字一样,都可以保证可见性
  2. 有序性:volatile可以保证有序性,避免指令编排的情况,依赖于操作系统的内存屏障
  3. 原子行 :volatile只能保证单个操作的原子性,不能保证一系列操作的原子性,不能保证线程安全,所以说volatile不能保证原则性

文章来源: smilenicky.blog.csdn.net,作者:smileNicky,版权归原作者所有,如需转载,请联系作者。

原文链接:smilenicky.blog.csdn.net/article/details/121292215

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。