可见性问题
多线程下修改共享变量可能会出现变量修改后的不可见性
通俗来讲,并发编程的可见性就是在多线程操作同一共享变量时,修改变量后的值不能实时更新到其他线程的工作空间,就会导致其他线程读取到的值是不准确的
这种情况被称之为并发编程下的变量不可见性,属于线程安全问题的一种。但是并不是我们只要多个线程操作共享变量就一定会出现不可见性
导致可见性的问题有很多:比如CPU的高速缓存、CPU的指令重排序、编译器的指令重排序
可见性举例:
public class Demo01 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
while (true) {
if (t.isFlag()) {
System.out.println("主线程访问子线程flag标志位为" + t.isFlag());
}
}
}
}
class MyThread extends Thread{
private boolean flag = false;
public boolean isFlag(){
return flag;
}
@Override
public void run() {
try {
Thread.sleep(10);
/* 为了使效果更明显此处模拟开发时执行该段程序消耗时间 子线程睡眠10ms导致cpu被主线程拿到所以主线程访问到的flag始终为false
*/
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
flag = true;
System.out.println("子线程flag ="+flag);
}
}
程序分析:这段程序中,线程类给定flag为false,同时又在子线程中修改flag为true,那么此时主线程访问到的flag有可能是子线程修改之前的false也有可能是修改之后的true。如果主线程访问到了flag为false则出现了不可见性问题
了解Java内存模型
Java内存模型,也就是JMM(Java Memory Model)是一种抽象的概念,主要阐述了Java中线程与主内存之间的关系和操作 ,也是接下来分析共享变量不可见性的主要分析依据
- JVM在运行Java程序时,实际就是在运行一条一条的线程,因此也会为每一条线程创建属于自己的工作内存用于存储数据
- Java的内存模型规定所有的变量都存储在主内存中(可以理解为是计算机中的总线),但是不包括局部变量
- 在程序运行时,每一条线程会向主内存中访问变量,并加载一份该变量的副本到自己的工作内存中进行操作,操作完成之后返回到主内存。也就是说线程不会直接操作主内存,只能对自身的工作内存中数据进行读写
- 各个线程之间是相互独立的关系,相互间的工作内存不会影响。可以认为各个线程之间的交互操作都会在主内存中完成
Java内存模型图:
共享变量 ?
- 所有的共享变量(实例变量、类变量)都存在于主内存中,线程需要访问时会从主内存中获取一份副本并加载到自己的工作内存中进行操作
了解JMM之后再次对上述示例分析:为什么出现了不可见性
首先线程类中定义的成员变量flag=false位于主内存中,此时无论子线程还是主线程访问到的都是flag=false。
然后子线程对flag进行了修改flag=true并且将新值送到了主内存中,此时主内存中更新flag=true但是此时主线程已经获取了flag=false所以无法打印
解决可见性问题
- 加锁
利用synchronized的同步代码块锁住可能会出现不可见性问题的线程代码,上述示例修改如下:
while (true) {
synchronized(volatileDemo.class){
if (t.isFlag()) {
System.out.println("主线程访问子线程flag标志位为" + t.isFlag());
}
}
}
加锁解决的原理:
加锁之后将会刷新该线程的工作内存,刷新之后从主内存获取到变量的最新值
- volatile关键字修饰
/* 只需要将共享变量加上volatile修饰即可 如下*/
private volatile boolean flag = false;
volatile关键字的原理:
volatile修饰的变量被多个线程访问时,一旦某一条线程修改了变量的值就会导致该变量的原值失效,此时所有访问该变量的线程都会重新去主内存中获取新值
volatile和synchronized
- volatile只能用于修饰类变量和实例变量,synchronized用于修饰方法以及代码块
- volatile保证数据的可见性,但是不保证原子性(线程进行写操作,不保证线程安全);而synchronized是一种(排他)互斥的机制