内存可见性、原子性及有序性

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/linfujian1999/article/details/86575143

计算机的存储结构如下图
图片来自网络
其中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的情况。

猜你喜欢

转载自blog.csdn.net/linfujian1999/article/details/86575143
今日推荐