Java内存模型(Java Memory Model简称JMM)是一种抽象的概念,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
Java内存模型中规定所有变量(不包括方法的本地变量)都存储在主内存,主内存是共享内存区域,所有线程都可以访问, 但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必 须通过主内存来完成
VisibilityTest
package com.cctv;
public class VisibilityTest {
private boolean flag = true;
public void refresh() {
flag = false;
System.out.println(Thread.currentThread().getName() + "修改flag");
}
public void load() {
System.out.println(Thread.currentThread().getName() + "开始执行.....");
int i = 0;
while (flag) {
i++;
//TODO 业务逻辑
}
System.out.println(Thread.currentThread().getName() + "跳出循环: i=" + i);
}
public static void main(String[] args) throws InterruptedException {
VisibilityTest test = new VisibilityTest();
// 线程threadA模拟数据加载场景
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
test.load();
}
}, "threadA");
threadA.start();
// 让threadA执行一会儿
Thread.sleep(1000);
// 线程threadB通过flag控制threadA的执行时间
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
test.refresh();
}
}, "threadB");
threadB.start();
}
}
JMM不同于JVM内存区域模型
JMM与JVM内存区域的划分是不同的概念层次,更恰当说JMM描述的是一组规则,通过这组规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式
JMM是围绕原子性,有序性、可见性展开,JMM与JVM内存区域唯一相似点,都存在共享数据区域和私有数据区域,在JMM中主内存属于共享数据区域,从某个程度上讲应该包括了堆和方法区,而工作内存数据属于线程私有数据区域,从某个程度上讲则应该包括程序计数器、虚拟机栈 以及本地方法栈。
主内存
主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发生线程安全问题
工作内存
主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝), 每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变 量,当然也包括了字节码行号指示器、相关Native方法的信息。
注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。
根据JMM规范,对于一个实例对象中的成员方法而言,如果方法中包含本地变量是基本数据类型(boolean,byte,short,char,int,long,float,double),将直接存储在工作内存的帧栈结构中,但倘若本地变量是引用类型,那么该变量的引用会存储在工作内存的帧栈中,而对象实例将存储在主内存(共享数据区域,堆)中。
但对于实例对象的成员变量,不管它是基本数据类型或者包装类型(Integer、Double等)还是引用类型,都会被存储到堆区。至于static变量以及类本身相关信息将会存储在主内存中。需要注意的是,在主内存中的实例对象可以被多线程共享,倘若两个线程同时调用了同一个对象的同一个方法,那么两条线程会将要操作的数据拷贝一份到自己的工作内存中,执行完成操作后才刷新到主内存
模型如下图所示
package com.cctv;
public class VisibilityTest1 {
public void refresh(User user) {
user.setFlag(false);
System.out.println(Thread.currentThread().getName() + "修改flag");
}
public void load(User user) {
System.out.println(Thread.currentThread().getName() + "开始执行.....");
int i = 0;
while (user.getFlag()) {
i++;
//TODO 业务逻辑
}
System.out.println(Thread.currentThread().getName() + "跳出循环: i=" + i);
}
public User getUserInstanct(){
User user = new User();
return user;
}
public static void main(String[] args) throws InterruptedException {
VisibilityTest1 test = new VisibilityTest1();
// 线程threadA模拟数据加载场景
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
test.load(test.getUserInstanct());
}
}, "threadA");
threadA.start();
// 让threadA执行一会儿
Thread.sleep(1000);
// 线程threadB通过flag控制threadA的执行时间
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
test.refresh(test.getUserInstanct());
}
}, "threadB");
threadB.start();
}
}
package com.cctv;
public class User {
private boolean flag = true;
public boolean getFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}