Java编程多线程面试精选

并发编程系列之什么是Java内存模型?

2021-12-21  本文已影响0人  smileNicky

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

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");
    }
}

执行,控制台打印:

设置flag

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

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");
    }
}

控制台打印:

设置flag
72071943

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");
    }
}

控制台打印:

设置flag
86726163

2、什么是Java内存模型?

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

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

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

  1. lock(锁定):将主内存中的变量锁定,为一个线程所独占
  2. unlock(解锁):将lock加的锁解除,其他的线程有机会访问此变量
  3. read(读取):作用于主内存变量,将主内存中的变量值读取到工作内存
  4. load(加载):作用于工作内存,将read读取到的值保存到工作内存中的变量副本
  5. use(使用):作用于工作内存变量,将值传递给线程的代码执行引擎
  6. assign(赋值):作用于工作内存变量,将执行引擎处理返回的值重新赋值给变量副本
  7. store(存储):作用于工作内存变量,将变量副本的值传送到主内存中
  8. write(写入):作用于主内存变量,将store传送过来的值写入到主内存的共享变量中
  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)
  1. 将一个变量从主内存复制到工作内存要顺序执行read、load操作;要将变量从工作内存同步回主内存要用store、write操作。只要求顺序执行,不一定是连续执行

图引用网上资料:


在这里插入图片描述

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

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

4、Synchronized怎么做到可见性

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

5、volatile关键字解密

  1. 使用volatile变量时,必须重新从主内存加载到工作内存,并且read、load是连续的
  2. 修改volatile变量后,必须马上同步回主内存,并且store、write是连续的
  1. 使用volatile比synchronized简单
  2. volatile性能比synchronized好
  1. volatile只能修饰成员变量
  2. 在多线程并发的场景才使用
  1. 可见性:volatile和synchronized关键字一样,都可以保证可见性
  2. 有序性:volatile可以保证有序性,避免指令编排的情况,依赖于操作系统的内存屏障
  3. 原子行 :volatile只能保证单个操作的原子性,不能保证一系列操作的原子性,不能保证线程安全,所以说volatile不能保证原则性
上一篇 下一篇

猜你喜欢

热点阅读