多线程原子性、可见性、有序性

  20 Jun 2017


并发编程中的三个概念

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
有序性:即程序执行的顺序按照代码的先后顺序执行
指令重排序:
	处理器为了提高程序运行效率,可能会对输入代码进行优化,
	它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,
	但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
	指令重排序不会影响单线程的执行,但是会影响到线程并发执行的正确性。
volatile:
可以保证可见性,不能保证原子性;可以保证一定的有序性

1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
synchronized,Lock
可见性,原子性,有序性
(保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中)
线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。

Java中的原子操作包括:

1)除long和double之外的基本类型的赋值操作
	Java内存模型只保证了基本类型读取和赋值是原子性操作(int等不大于32位的类型上的操作都是原子操作),
	如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。
2)所有引用reference的赋值操作
3)java.concurrent.Atomic.* 包中所有类的一切操作

long和double的赋值操作是非原子操作

long和double占用的字节数都是8,也就是64bits。在32位操作系统上对64位的数据的读写要分两步完成,每一步取32位数据。

volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。
java的内存模型保证声明为volatile的long和double变量的get和set操作是原子的。
注意:
count++不是原子操作,是3个原子操作组合
	1.读取主存中的count值,赋值给一个局部成员变量tmp
	2.tmp+1
	3.将tmp赋值给count
可能会出现线程1运行到第2步的时候,tmp值为1;这时CPU调度切换到线程2执行完毕,count值为1;
切换到线程1,继续执行第3步,count被赋值为1--结果就是两个线程执行完毕,count的值只加了1;

还有一点要注意,如果使用AtomicInteger.set(AtomicInteger.get() + 1),会和上述情况一样有并发问题;
要使用AtomicInteger.getAndIncrement()才可以避免并发问题

使用volatile关键字的场景

1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
参考:

http://www.cnblogs.com/dolphin0520/p/3920373.html