高并发编程:15、缓存一致性,volatile关键字(二)

一、概述

  • 能保证修饰变量的可见性、有序性,但不能保证原子性。

二、分析

1、不能保证原子性

public class Thread17 {

	private static volatile Integer INIT_VALUE = 0;
	private static Integer MAX_LIMIT = 500;
	
	public static void main(String[] args) {
		test2();
	}
	
	public static void test2() {
		new Thread(new Runnable() {
			public void run() {
				while(INIT_VALUE < MAX_LIMIT) {
					++INIT_VALUE;
					System.out.println("read : " + INIT_VALUE);
				}
			}
		},"read").start();
		
		try {
			Thread.sleep(10);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		new Thread(new Runnable() {
			public void run() {
				while (INIT_VALUE < MAX_LIMIT) {
					++INIT_VALUE;
					System.out.println("update : " + INIT_VALUE);
					
				}
			}
		},"update").start();
	}
...
read : 378
read : 379
read : 380
read : 381
read : 382
read : 383
read : 384
update : 384
update : 386
update : 387
read : 385
read : 389
read : 390
update : 388
read : 391
...
  • 当INIT_VALUE用volatile修饰,或不用volatile修饰都会出现两个相同的值如:384
  • 这是因为++INIT_VALUE实际上是由三部分组成,这三部在多线程下的组合并不是原子性:
  • 1、从主内存中获取INIT_VALUE的值。
  • 2、进行运算。
  • 3、重新赋值给INIT_VALUE变量。
  • 如:当INIT_VALUE为383的时候,read线程运行完2之后,还没执行3。
  • 线程cpu的执行权被切换到udpate线程,然后执行++INIT_VALUE操作,读取到的INIT_VALUE依然为383,所以执行完++INIT_VALUE之后输出的值为384。
  • 当cpu的执行权再次切换到read线程的时候,继续执行第3步骤,输出的值也为384,所以看到输出了两次384。
  • 证明了volatile并不能保证原子性。

2、可见性已在上一篇文章中已介绍:https://blog.youkuaiyun.com/oJueQiang123456/article/details/100867699

3、有序性,先看一段代码:

public class VolatileTest {
	private boolean isInit = false;
	public VolatileTest() {
		// 1、初始化程序
		init();
		// 2、设置为初始化完成
		isInit = true;
	}
	public void init() {
		
	}
	public void update() {
		if(isInit) {
			// 执行业务代码
		}
	}
}

逻辑顺序是:初始化程序、设置isInit为已初始化,执行update业务,看起来逻辑没有问题,在单线程下确实没问题,但在多线程下就会出现问题,如:因为jvm会对程序进行重排序(具体重排序规则查看网络资料),重排序之后的代码可能为:

public VolatileTest() {
	// 2、设置为初始化完成
        isInit = true;
	// 1、初始化程序
	init();
}

jvm会保证最终一致性,执行完构造方法之后,当为单线程时,调用update业务不会有任务问题,但在多线程下重排序之后下:

  • 当执行完2之后:isInit = true,但程序还未初始化。
  • cpu执行权切换到其它线程去执行update业务的时候程序还未初始化,导致程序有问题

所以可以在变量isInit中添加volatile关键字修饰来保证程序的有序性:这样在isInit被修改之前,一定会保证之前的代码全部执行完成了,这样就不会不会出现重排导致的问题了,具体的指令重排还有很多内容,比如happens-before原则,内存屏障等等

private volatile boolean isInit = false;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值