现象
private static boolean is = false;public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread 1 start");while (!is) {}System.out.println("thread 1 end");}}).start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}new Thread(new Runnable() {@Overridepublic void run() {System.out.println("thread 2 start");is = true;System.out.println("thread 2 end");}}).start();}
}
以上代码的系统输出内容
现在,对我们的变量加入voliate关键字后输出对比
从上面的对比我们不难看出,没有加入voliate的时候,我们的程序一直没有退出,因为线程1一直处于死循环。
也就是说,线程2改变的is值没能成功传递到线程1
本质
要说明这个问题,得从jvm的工作内存和主内存说起
下面是copy的一段内容
JVM将内存组织为主内存和工作内存两个部分。
主内存是所有的线程所共享的,主要包括本地方法区和堆。
每个线程都有一个工作内存不是共享的,工作内存中主要包括两个部分:
1:一个是属于该线程私有的栈;2:对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。
1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的
2.每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成,即:线程、主内存、工作内存。
画个图来理解一下这段话的意思
图解
当A和B对主内存的变量进行操作的时候,它们并不是直接操作这个X,而是要先经过一次内存拷贝,将X拷贝到自己的工作内存中
当某个线程对X进行写入操作的时候,是对自己的X副本进行写入,然后再将这个副本的内容交给系统写入到主内存。但是呢,如果这个时候,B已经从主内存中读取了X变量了,那么B每次拿,都是拿自己的X的副本,也就是之前的值
所以,B线程是没办法拿到修改后的值。
关于voliate
写过程: 当一个线程修改某个voliate变量的值的时候,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
读过程: 当一个线程读取某个voliate变量的值的时候,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存读取共享变量。
这样就保证了,B线程能够得到修改后的值了。