原子性问题:
在一个线程中,对一个32的二进制数进行赋值操做,当低16位的数据写入后,发生了中断,而此时又有一个线程去读取这个写入的数据,一定获得的是一个错误的数据。
在java中这种状况是不存在的,由于对基本数据类型的写入和赋值保证了原子性(i=10)。但仅限制于对基本数据类型,而变量的赋值就不能保证,自增就是个很好的例子,
i++:
自增:先读取数据,再加1,再写入.
也就是说只有简单的读取,赋值才是原子性。并且赋值必须是将数字赋给某个变量,变量之间的相互赋值不是原子性的,若是要实现更大范围操做的原子性,能够经过能够经过synchronized和Lock来实现。
因为synchronized和Lock可以保证任一时刻只有一个线程执行该代码块,那么天然就不存在原子性问题了,从而保证了原子性。
可见性问题:
你们都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程当中,势必涉及到数据的读取和写入。因为程序运行过程当中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,因为CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,所以若是任什么时候候对数据的操做都要经过和内存的交互来进行,会大大下降指令执行的速度。所以在CPU里面就有了高速缓存。
当程序运行过程当中,会将须要的数据从主内存复制一份给cpu的高速缓存,cpu进行计算时就能够直接从高速缓存读取和写入数据,运算结束,把结果刷新到主存中。
线程一:int i=0; i=10; 线程二:j = i;
倘若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,而后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有当即写入到主存当中。
此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值仍是0,那么就会使得j的值为0,而不是10.
在java中,volatile关键字能够保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会当即被更新到主存,当有其余线程须要读取时,它会去内存中读取新值。
另外,经过synchronized和Lock也可以保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁而后执行同步代码,而且在释放锁以前会将对变量的修改刷新到主存当中。所以能够保证可见性。
有序性问题:
int a = 10; //语句1 int r = 2; //语句2 a = a + 3; //语句3 r = a*a; //语句4 这四条语句不必定按照顺序执行。 通常来讲,处理器为了提升程序运行效率,可能会对输入代码进行优化,进行指令重排序。它不保证程序中各个语句的执行前后顺序同代码中的顺序一致,可是它会保证程序最终执行结果和代码顺序执行的结果是一致的。 处理器在进行重排序时是会考虑指令之间的数据依赖性,若是一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2以前执行。 虽然重排序不会影响单个线程内程序执行的结果,可是多线程呢? 指令重排序不会影响单个线程的执行,可是会影响到线程并发执行的正确性。 1.volatile关键字的两层语义 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义: 1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。 2)禁止进行指令重排序。 可是用volatile修饰以后就变得不同了: 第一:使用volatile关键字会强制将修改的值当即写入主存; 第二:使用volatile关键字的话,当线程2进行修改时,会致使线程1的工做内存中缓存变量缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效); 第三:因为线程1的工做内存中缓存变量缓存行无效,因此线程1再次读取变量的值时会去主存读取。 那么在线程2修改值时(固然这里包括2个操做,修改线程2工做内存中的值,而后将修改后的值写入内存),会使得线程1的工做内存中缓存变量的缓存行无效,而后线程1读取时,发现本身的缓存行无效,它会等待缓存行对应的主存地址被更新以后,而后去对应的主存读取最新的值。 那么线程1读取到的就是最新的正确的值。 volatile没办法保证对变量的操做的原子性。 在前面提到volatile关键字能禁止指令重排序,因此volatile能在必定程度上保证有序性。 volatile关键字禁止指令重排序有两层意思: 1)当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且结果已经对后面的操做可见;在其后面的操做确定尚未进行; 2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。 //x、y为非volatile变量 //flag为volatile变量 x = 2; //语句1 y = 0; //语句2 flag = true; //语句3 x = 4; //语句4 y = -1; //语句5 因为flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句一、语句2前面,也不会讲语句3放到语句四、语句5后面。可是要注意语句1和语句2的顺序、语句4和语句5的顺序是不做任何保证的。 而且volatile关键字能保证,执行到语句3时,语句1和语句2一定是执行完毕了的,且语句1和语句2的执行结果对语句三、语句四、语句5是可见的。