首先,JVM中堆空间(存放对象)、方法区(存放静态变量、常量)、常量池(String常量池、整形常量池)是线程共享的空间,Java栈空间是线程私有的,每个线程都有一个栈空间,执行一个方法时会创建一个栈帧,压入栈中,栈帧中保存局部变量、方法参数、中间变量的值,方法返回后栈帧弹出。线程读取堆、方法区、常量池中数据后,存放在线程栈中,对栈中的值操作完成后,再写回堆、方法区、常量池中,如果多个线程同时改动共享数据的话,可能会出现一个线程操作后的结果被其他线程返回的结果覆盖掉(同时写)或者一个线程读取到的数据是其他线程改动后的结果(一个线程读、一个线程写)。所以要对共享数据(共享内存中的数据)进行同步,一个时刻只能有一个线程进行写操作,该线程执行完成后,其他线程才能访问。
例如下面的例子,1000个线程同时对共享变量count进行加1,所有线程执行结束后,count值理应是1000,然而结果并非总是1000.
public class Main {
public static int count = 0;
public static void countNum() {
//休眠一毫秒,结果更明显
try {
Thread.sleep(1);
} catch (Exception e) {
}
count++;
}
public static void main(String[] args) {
//同时启动1000个线程,每个线程加1,观察实际结果是否是1000
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
Main.countNum();
}
}).start();
}
//主线程休眠2秒钟,保证1000个线程已经执行完成
try {
Thread.sleep(2000);
} catch (Exception e) {
}
//每次运行的结果都有可能不同,可能1000
System.out.println("结果是:" + Main.count);
}
}
如果线程1从方法区(线程共享空间)复制变量到当前线程栈内存后,修改完成,还没写回方法区前,此时其他线程执行了写操作并写回了方法区,然后线程1将结果写回方法区,会导致其他线程的操作结果被线程1覆盖。