这一点,跟数据库事务的原子性概念差很少,即一个操做(有可能包含有多个子操做)要么所有执行(生效),要么所有都不执行(都不生效)。数据库
关于原子性,一个很是经典的例子就是银行转帐问题:好比A和B同时向C转帐10万元。若是转帐操做不具备原子性,A在向C转帐时,读取了C的余额为20万,而后加上转帐的10万,计算出此时应该有30万,但还将来及将30万写回C的帐户,此时B的转帐请求过来了,B发现C的余额为20万,而后将其加10万并写回。而后A的转帐操做继续——将30万写回C的余额。这种状况下C的最终余额为30万,而非预期的40万。编程
可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程可以当即看到。可见性问题是好多人忽略或者理解错误的一点。缓存
CPU从主内存中读数据的效率相对来讲不高,如今主流的计算机中,都有几级缓存。每一个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会当即更新该缓存,但并不必定会当即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤为是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。bash
这一点是操做系统或者说是硬件层面的机制,因此不少应用开发人员常常会忽略。多线程
顺序性指的是,程序执行的顺序按照代码的前后顺序执行。并发
经常使用的保证Java操做原子性的工具是锁和同步方法(或者同步代码块)。使用锁,能够保证同一时间只有一个线程能拿到锁,也就保证了同一时间只有一个线程能执行申请锁和释放锁之间的代码。app
public void testLock () {
lock.lock();
try{
int j = i;
i = j + 1;
} finally {
lock.unlock();
}
}
复制代码
与锁相似的是同步方法或者同步代码块。使用非静态同步方法时,锁住的是当前实例;使用静态同步方法时,锁住的是该类的Class对象;使用静态代码块时,锁住的是synchronized关键字后面括号内的对象。下面是同步代码块示例工具
public void testLock () {
synchronized (anyObject){
int j = i;
i = j + 1;
}
}
复制代码
不管使用锁仍是synchronized,本质都是同样,经过锁来实现资源的排它性,从而实际目标代码段同一时间只会被一个线程执行,进而保证了目标代码段的原子性。这是一种以牺牲性能为代价的方法。性能
Java提供了volatile关键字来保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会当即被更新到内存中,而且将其它缓存中对该变量的缓存设置成无效,所以其它线程须要读取该值时必须从主内存中读取,从而获得最新的值。spa
上文讲过编译器和处理器对指令进行从新排序时,会保证从新排序后的执行结果和代码顺序执行的结果一致,因此从新排序过程并不会影响单线程程序的执行,却可能影响多线程程序并发执行的正确性。
Java中可经过volatile在必定程序上保证顺序性,另外还能够经过synchronized和锁来保证顺序性。
synchronized和锁保证顺序性的原理和保证原子性同样,都是经过保证同一时间只会有一个线程执行目标代码段来实现的。
除了从应用层面保证目标代码段执行的顺序性外,JVM还经过被称为happens-before原则隐式地保证顺序性。两个操做的执行顺序只要能够经过happens-before推导出来,则JVM会保证其顺序性,反之JVM对其顺序性不做任何保证,可对其进行任意必要的从新排序以获取高效率。
volatile适用于不须要保证原子性,但却须要保证可见性的场景。适合用于单线程写,多线程读数据的场合。另外一种典型的使用场景是用它修饰用于中止线程的状态标记。以下所示
boolean isRunning = false;
public void start () {
new Thread( () -> {
while(isRunning) {
someOperation();
}
}).start();
}
public void stop () {
isRunning = false;
}
复制代码
在这种实现方式下,即便其它线程经过调用stop()方法将isRunning设置为false,循环也不必定会当即结束。能够经过volatile关键字,保证while循环及时获得isRunning最新的状态从而及时中止循环,结束线程。
关键字volatile是线程同步的轻量级实现,因此volatile性能确定比synchronized要好,而且volatile只能修饰变量,而synchronized能够修饰方法,以及代码块。
多线程访问volatile不会发生堵塞,而synchronized可能会出现堵塞。
volatile保证数据的可见性,但不能保证原子性;而synchronized能够保证原子性,也能够间接保证可见性,由于它会将私有内存和公共内存中的数据作了同步。
关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些状况下性能要优于synchronized,可是要注意volatile关键字是没法替代synchronized关键字的,由于volatile关键字没法保证操做的原子性。
一般来讲,使用volatile必须具有如下2个条件:
1)对变量的写操做不依赖于当前值。
2)该变量没有包含在具备其余变量的不变式中。