volatile看起来简单,但是要想理解它还是比较难的,这里只是对其进行基本的了解。
通俗点讲就是说一个变量如果用volatile修饰了,则Java可以确保所有线程看到这个变量的值是一致的,如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立马看到这个更新,这就是所谓的线程可见性。
volatile相对于synchronized稍微轻量些,在某些场合它可以替代synchronized,但是又不能完全取代synchronized,只有在某些场合才能够使用volatile。使用它必须满足如下两个条件:
对变量的写操作不依赖当前值;
该变量没有包含在具有其他变量的不变式中。
volatile经常用于两个两个场景:状态标记量、double check Lock。
在并发编程中我们一般都会遇到这三个基本概念:原子性、可见性、有序性。我们稍微看下volatile
原子性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
原子性就像数据库里面的事务一样,他们是一个团队,同生共死。其实理解原子性非常简单,我们看下面一个简单的例子即可:
i = 0; ---1
j = i ; ---2
i++; ---3
i = j + 1; ---4
上面四个操作,有哪个几个是原子操作,那几个不是?如果不是很理解,可能会认为都是原子性操作,其实只有1才是原子操作,其余均不是。
1—在Java中,对基本数据类型的变量和赋值操作都是原子性操作;
2—包含了两个操作:读取i,将i值赋值给j
3—包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;
4—同三一样
在单线程环境下我们可以认为整个步骤都是原子性操作,但是在多线程环境下则不同,Java只保证了基本数据类型的变量和赋值操作才是原子性的(注:在32位的JDK环境下,对64位数据的读取不是原子性操作*,如long、double)。要想在多线程环境下保证原子性,则可以通过锁、synchronized来确保。
volatile是无法保证复合操作的原子性
可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
在上面已经分析了,在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。
Java提供了volatile来保证可见性。
当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,当其他线程读取共享变量时,它会直接从主内存中读取。
当然,synchronize和锁都可以保证可见性。
有序性
有序性:即程序执行的顺序按照代码的先后顺序执行。
在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序它不会影响单线程的运行结果,但是对多线程会有影响。
Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。这里可以理解为,当两个处理器针对一个volatile做操作时,一个在Store而另一个在Load时,
因为fence()的lock使得Store和Load不得不错开,而Load自然会推迟到Store完成之后,所以才有了这个Happen-Before原则:
volatile变量的写先于volatile变量的读.
public class ArmyRunnable implements Runnable{
//ensure the thread can read exactly the value of other thread write
volatile boolean keepRunning = true;
@Override
public void run() {
while(keepRunning) {
//Attack 5 times
for(int i=0; i<5;i++) {
System.out.println(Thread.currentThread().getName()+" Attack "+i+"st");
Thread.yield();
}
}
System.out.println(Thread.currentThread().getName()+"Finish Attck !");
}
}
public class KeyPersonThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName()+" start fight!");
for(int i=0; i<10;i++) {
System.out.println(Thread.currentThread().getName()+" fight Evil "+ i+ "st");
}
System.out.println(Thread.currentThread().getName()+" finish fight!");
}
}
public class Stage extends Thread {
public void run() {
ArmyRunnable armyTaskOfGod = new ArmyRunnable();
ArmyRunnable armyTaskOfEvil = new ArmyRunnable();
Thread armyOfGod = new Thread(armyTaskOfGod,"God ");
Thread armyOfEvil = new Thread(armyTaskOfEvil,"Evil");
armyOfGod.start();
armyOfEvil.start();
//Stage Thread Sleep
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Suddenly, Heo comes.");
Thread keyPerson = new KeyPersonThread();
keyPerson.setName("Hero");
armyTaskOfGod.keepRunning = false;
armyTaskOfEvil.keepRunning = false;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
keyPerson.start();
try {
keyPerson.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//
// try {
// armyOfGod.join();
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
System.out.println("Finish");
}
public static void main(String[] args) {
new Stage().start();
}
}