Java并发三大性质
在Java内存模型中,有三大性质:原子性、有序性和可见性.
原子性:即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰.
有序性:即程序执行的顺序按照代码的先后顺序执行.
可见性:当多个线程访问同一个变量的时候,一旦线程修改了这个变量的值,其他线程能够李可看到修改的值。
原子性
举个例子:A想要从自己的帐户中转1000块钱到B的帐户里。那个从A开始转帐,到转帐结束的这一个过程,称之为一个事务.在这个事务里,要做如下操作:
从A的帐户中减去1000块钱。如果A的帐户原来有3000块钱,现在就变成2000块钱了。
在B的帐户里加1000块钱。如果B的帐户如果原来有2000块钱,现在则变成3000块钱了。
如果在A的帐户已经减去了1000块钱的时候,忽然发生了意外,比如停电什么的,导致转帐事务意外终止了,而此时B的帐户里还没有增加1000块钱。那么,我们称这个操作失败了,要进行回滚。
我们把这种要么一起成功,要么一起失败的操作叫原子性操作。
如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行.这种特性就叫原子性。
举一个精确的例子:
int x = 1; int y = x; x++; x += 1;
以上四句代码,一眼看去都是原子性操作,其实里面只有第1句是原子性操作.
首先我们来了解下计算机的工作方式:
在我们运行程序的时候,CPU会执行程序的每条指令,在执行程序的过程中,肯定会涉及到一些临时数据,这些数据是存放在内存中的。这就存在一个问题,CPU执行指令的速度比从内存中读取数据和向内存中写入数据快很多.如果任何时候操作数据都要从内存中读写的话,会大大降低程序的运行速度,因此就有了高速缓存:
在程序运行过程中,会将运算需要的数据从内存复制一份到CPU的高速缓存中,当CPU计算的时候,就可以直接从它的高速缓存中读写数据。结束运算后,再将高速缓存中的数据写入内存中。
我们再回来看一下刚才的四句代码:
int x=1;//给x赋值为1,这个操作会直接将10写入内存中
int y=x;//这其实是两个操作,先读取x的值,然后将x的值赋值给y再写入内存中,这两个操作虽然都是原子性操作,但是合起来就不是原子性操作了
x++; x+=1;//这两句代码包含3个操作,先去读取x的值,然后进行加1,在把新值写入到内存中
但是注意一个问题,第一句代码:
int x=1;//假设x是一个32位的变量,那为它赋值包含两个过程,给低16位赋值,给高16位赋值,所以也不是原子性操作
可见性
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰的时候,他会保证对值的修改,会立刻被其他共享的线程看见,从而下次加载的时候会从内存中重新取值,而不是从高速缓存中取值,但是volatile变量不能保证原子性。
如果想保证原子性,可以使用synchronized和ReentrantLock,他们既可以保证可见性,也可以保证原子性,但是开销会大很多。
有序性
在Java中,允许编译器和处理器对指令进行重排序,什么叫重排序,说简单点就是改变指令的执行顺序。重新排序不会影响单线程执行,却会影响到多线程并发执行。
同样,可以使用synchronized和ReentrantLock,他们也会保证有序性。
但是,我们今天讲点额外的东西,Java内存模型中具有一些先天的有序性。也称为happens-before原则。
happens-before原则:
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
转自:https://www.cnblogs.com/dolphin0520/p/3920373.html