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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frank Lin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值