计算机的存储结构如下图
其中cache高速缓存是cpu的一部分,一般cpu操作内存的数据(读写)会先判断数据是否在cache上有副本,有的话避免操作主内存直接从cache上操作副本。然后再在特定时机将cache的数据同步至主内存。
内存数据可见性:
单核cpu不存在可见性问题,因为所有的数据操作均是操作同一cache上的数据,但目前的应用服务器一般是多核cpu,这样每一个cpu上均有一个cache,主内存的同一数据在不同的cache上的副本在同一时间存在不一致的可能性。
见如下代码(可见性部分):
public class DemoController {
public static boolean flag = false;
public static volatile int increment = 0;
public static void main(String[] args) {
//可见性
new Thread(()->{
while(!flag) {
}
System.out.println("thread B end");
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(()-> {
flag = true;
System.out.println("thread A end");
}).start();
//原子性
Executor executor = Executors.newCachedThreadPool();
for(int i=0; i<5; i++) {
executor.execute(()->{
for(int j=0; j<1000; j++) {
increment++;
}
});
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(increment);
}
}
输出“thread A end”,第一个线程由于flag一直是cache中的flase,所有while循环一直没有结束。所以在成员变量flag上加关键字volatile
public static volatile boolean flag = false;
可见性保证了是否保证了原子性呢,原子性是一个操作要么不执行要么一次性执行完毕,看如上代码的第二个原子性例子。
public static volatile int increment = 0;
成员变量increment保证了可见性,但运行结果未必是5000,一般小于5000.
因为increment++并非原子性操作,它有三部分组成:1)主内存中取increment的值;2)+1;3)将新值赋值给increment
多线程读取到的increment保证是主内存中的最新值,但接下来的increment++可能都是在同一个值得基础上做加和操作,因此得到的increment最终一定小于5000
为了保证原子性操作,可修改为如下:
//原子性
Executor executor = Executors.newCachedThreadPool();
for(int i=0; i<5; i++) {
executor.execute(()->{
for(int j=0; j<1000; j++) {
synchronized (executor) {
increment++;
}
}
});
}
或者
public static volatile AtomicInteger increment = new AtomicInteger(0);
//原子性
Executor executor = Executors.newCachedThreadPool();
for(int i=0; i<5; i++) {
executor.execute(()->{
for(int j=0; j<1000; j++) {
increment.getAndAdd(1);
}
});
}
有序性是这样的
//线程1:
Context context = loadContext(); //语句1
initial = true; //语句2
//线程2:
while(!initial) {
sleep();
}
dosomethingwithContext(context);
语句1、2之间没有数据依赖关系,在cpu执行时可能发生重排序,语句2先执行,然后线程2判断initial为true后跳出while循环,开始根据context进行操作但是报错,解决这个有序性的问题就在于happens_before原则,volatile 关键字修饰的变量的写发生在该变量读之前,所以给context变量加volatile关键字,即能保证context为null的情况。