package com.victor.hello; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class VolatileTest { private static volatile int volatileCounter = 0; private static int noneVolatileCounter = 0; public static void main(String[] args){ final ScheduledExecutorService service = Executors.newScheduledThreadPool(10); for(int i =0;i<10;i++){ service.scheduleAtFixedRate(new Runnable(){ @Override public void run() { String threadName = Thread.currentThread().getName(); volatileCounter++; sleep(); volatileCounter--; noneVolatileCounter++; sleep(); noneVolatileCounter--; System.out.println(volatileCounter+" "+noneVolatileCounter+" ["+threadName+"]"); } }, 0, 3, TimeUnit.SECONDS); } } private static void sleep(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
为了体验Volatile这个关键字的做用,我写了一个测试方法。两个int类型的变量,分别用volatile和不用volatile修饰。先作一个++的操做,再作一个--的操做。之间休息0.1秒。起十个线程,定时的操做。
java
有经验的同窗一看就知道,这么操做以为线程不安全。让咱们看看执行的结果。安全
0 9 [pool-1-thread-9]多线程
0 8 [pool-1-thread-7]并发
0 7 [pool-1-thread-5]ide
0 6 [pool-1-thread-3]性能
0 5 [pool-1-thread-1]测试
0 4 [pool-1-thread-2]this
0 3 [pool-1-thread-4]spa
0 2 [pool-1-thread-8]线程
0 1 [pool-1-thread-6]
0 0 [pool-1-thread-10]
1 9 [pool-1-thread-3]
1 8 [pool-1-thread-5]
1 7 [pool-1-thread-9]
1 6 [pool-1-thread-7]
1 5 [pool-1-thread-2]
1 4 [pool-1-thread-4]
1 3 [pool-1-thread-1]
1 2 [pool-1-thread-6]
1 1 [pool-1-thread-8]
1 0 [pool-1-thread-10]
1 9 [pool-1-thread-1]
1 8 [pool-1-thread-3]
1 7 [pool-1-thread-5]
1 5 [pool-1-thread-9]
1 5 [pool-1-thread-7]
1 4 [pool-1-thread-4]
1 3 [pool-1-thread-8]
1 2 [pool-1-thread-2]
1 1 [pool-1-thread-6]
1 0 [pool-1-thread-10]
1 9 [pool-1-thread-7]
1 8 [pool-1-thread-5]
1 7 [pool-1-thread-9]
1 6 [pool-1-thread-8]
1 5 [pool-1-thread-4]
1 4 [pool-1-thread-1]
1 3 [pool-1-thread-2]
1 2 [pool-1-thread-3]
1 1 [pool-1-thread-6]
1 0 [pool-1-thread-10]
1 9 [pool-1-thread-3]
1 8 [pool-1-thread-7]
1 7 [pool-1-thread-5]
1 6 [pool-1-thread-4]
1 5 [pool-1-thread-8]
1 4 [pool-1-thread-1]
1 3 [pool-1-thread-9]
1 3 [pool-1-thread-2]
1 1 [pool-1-thread-6]
1 1 [pool-1-thread-10]
1 9 [pool-1-thread-7]
1 8 [pool-1-thread-3]
1 7 [pool-1-thread-9]
1 7 [pool-1-thread-5]
1 6 [pool-1-thread-1]
1 5 [pool-1-thread-4]
1 4 [pool-1-thread-8]
1 3 [pool-1-thread-2]
1 2 [pool-1-thread-6]
1 1 [pool-1-thread-10]
1 10 [pool-1-thread-4]
1 9 [pool-1-thread-7]
1 8 [pool-1-thread-1]
1 7 [pool-1-thread-3]
1 6 [pool-1-thread-8]
1 5 [pool-1-thread-2]
1 4 [pool-1-thread-9]
1 2 [pool-1-thread-5]
1 2 [pool-1-thread-10]
1 1 [pool-1-thread-6]
可见,每次并发的时候。Volatile的修改都能迅速的让其余线程感知到。也就是线程间的可见性。
但几回并发之后,它就忍不住线程不安全了。可见并无保证线程的安全。
在解答这个问题以前,先说一下JAVA的内存模型,先看一张图
JAVA中的内存主要分主内存和线程工做内存。
主内存就是平时谈论最多的JVM的内存。
线程工做内存就是咱们平时所说的线程独享内存。你们都知道每一个线程有本身一块单独的内存。
每一次任务的执行都要执行以上几个操做(Read,load,use,asign,store,write)。
如图所示,其中load,use,asign,store动做都是在线程独享内存中发生的,并不会同步到主内存中。最后write时才会写会到主内存。
因此,在load,use,asign,store中变量的修改都是只发生在当前内存的,并不会被其余线程所看到,由于是线程独享的。
那么Volatile关键字的做用就是在load,use,asign,store动做的时候当即会将值同步到主内存,让其余线程当即能够看到。这也就是上面所说的可见性。
虽然保证了可见性,但并无作互斥的保证,这也就是为何多线程并发的时候,并不能保证线程的原子性。
使用Volatile有两个条件:
该变量的写操做不依赖当前的值
该变量没有包含在其余变量的不变式中
第一个比较好理解,例如++操做,就不符合第一个要求。由于++会先读取再写入。显然依赖了当前的值。
因此最开始咱们的例子当中,对于volatile修饰的变量作了++和--的操做显然是不合适的。
第二个举个例子
private volatile int volatileCounter = 1; private final int total = 100 + volatileCounter;
假设咱们有一个变量叫total,是100+volatileCounter的值。这样作也是不合适的。由于违反了第二条约定。
结合上面提到的两个使用条件,使用volatile做为标志位是很是合适的,并且会比使用synchronized修饰会容易和效率的多。
volatile boolean shutdownRequested = false; public void shutdown(){ shutdownRequested = true; } public void doWork(){ while(shutdownRequested){ //do shutdown } }
在多线程环境下,为了不多个线程同时去作关闭动做。能够用一个volatile修饰的shutdownRequested标志。这种作法要比使用synchronized容易和高效得多。
最经典的例子就是单例模式。若是要保证并发状况下单例,能够用Volatile修饰。以下
//注意用volatile修饰 private volatile static Singleton singleton; public static Singleton getInstance(){ //第一次检查 if(singleton == null){ synchronized(Singleton.class){ //第二次检查 if(singleton == null) { singleton = new Singleton(); } } } return singleton; }
独立观察有点像温度观测站,一边负责收集温度,一边负责按期的汇报当前温度
private volatile String temperature; //汇报当前的温度 public String getReport(){ return "当前温度是"+temperature+"度"; } //收集当前温度,能够多个站点并发的收集 private void doCollect(){ while(true){ String currentTemperature = getTemp(); temperature = currentTemperature; } }
既然一个参数能够是Volatile类型的,那么咱们也能够构造一个volatile类型的bean. 很好理解,再也不解释了。
@ThreadSafe public class Person { private volatile String firstName; private volatile String lastName; private volatile int age; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setAge(int age) { this.age = age; } }
当读的调用量远远超过写的时候,咱们能够考虑使用内部锁和volatile的组合来减小锁竞争带来的额外开销。
使用synchronized来控制自增的并发。可是getValue的方法只用了volatile修饰的返回值。大大的增长了并发量。由于synchronized每次只能有一个线程能访问,可是volatile却能够同时被多个线程访问。
@ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; //读操做,没有synchronized,提升性能 public int getValue() { return value; } //写操做,必须synchronized。由于x++不是原子操做 public synchronized int increment() { return value++; }
上面五个场景可能会有人说都比较相似或者接近。若是仔细观察能够发现,都有几个共同的特色:
或者对于参数的读取,并不存在依赖性(指依赖上一次的结果)
对于写入的方法仍是须要并发的控制,若是要作依赖的操做,如++,单例。若是是独立的操做,不依赖以前的结果,能够不用作并发控制。
参数的读取,并发性和实时性很是好。