Volatile特性
1.可见性:当一条线程对volatile变量进行了修改操做时,其余线程能当即知道修改的值,即当读取一个volatile变量时老是返回最近一次写入的值
2.原子性:对于单个voatile变量其具备原子性(能保证long double类型的变量具备原子性),但对于i ++ 这类复合操做其不具备原子性(见下面分析)
使用volatile变量的前提
1.对变量的写入操做不依赖变量的当前值,或者可以确保只有单一的线程修改变量的值
2.该变量不会与其余状态变量一块儿归入不变性条件中
3.在访问变量时不须要加锁
volatile可见性
volatile的可见性正是基于happend -before(先行发生)关系实现的。
happend-before:java内存模型有八条能够保证happend-before的规则(详见《深刻理解Java虚拟机》P376),若是两个操做之间的关系没法从这八条规则中推导出来的话,它们就没有顺序保障,虚拟机就能够对它们随意地进行重排序.
其中就包含”volatile变量规则“:对一个volatile变量的写操做先行发生于后面对这个变量的读操做,此规则保证虚拟机不会对volatile读/写操做进行重排序。
经过一个例子来了解vloative的可见性
例1:
public class VolatileTest extends Thread{
private boolean isRunning = true;
public boolean isRunning(){
return isRunning;
}
public void setRunning(boolean isRunning){
this.isRunning= isRunning;
}
public void run(){
System.out.println("进入了run...............");
while (isRunning){}
System.out.println("isUpdated的值被修改成为false,线程将被中止了");
}
public static void main(String[] args) throws InterruptedException {
VolatileTest volatileThread = new VolatileTest();
volatileThread.start();
Thread.sleep(1000);
volatileThread.setRunning(false); //中止线程
}
}
输出:
发现并无输出”isUpdated的值被修改成为false,线程将被中止了”这一句,说明经过setRunning来修改isRunning的值对于该程序是不可见的,也就是说程序不知道本身的值被修改了,为何?
缘由:Java内存模型(JMM)规定了全部的变量都存储在
主内存中,主内存中的变量为共享变量,而每条线程都有本身的
工做内存,线程的工做内存保存了从主内存拷贝的变量,全部对变量的操做都在本身的工做内存中进行,完成后再刷新到主内存中,回到例1,第18行号代码主线程(线程main)虽然对isRunning的变量进行了修改且有刷新回主内存中(
《深刻理解java虚拟机》中关于主内存与工做内存的交互协议提到变量在工做内存中改变后必须将该变化同步回主内存),但volatileThread线程读的还是本身工做内存的旧值致使出现多线程的可见性问题,解决办法就是给isRunning变量加上volatile关键字。
当变量被声明成volatile类型后,线程对该变量进行修改后会当即刷新回主内存,而其余线程读取该变量时会先将本身工做内存中的变量置为无效,再从主内存从新读取变量到本身的工做内存,这样就避免发生线程可见性问题。
volatile内存语义总结以下
1.当线程对volatile变量进行写操做时,会将修改后的值刷新回主内存
2.当线程对volatile变量进行读操做时,会先将本身工做内存中的变量置为无效,以后再经过主内存拷贝新值到工做内存中使用。
volatile原子性
volatile并不彻底具备原子性,对于复合操做其仍存在线程不安全的问题,如
例2
public class VolatileTest1{
private volatile int value; //将value变量声明成volatile类型
public void increment(){
value ++;
System.out.println(value);
}
public static void main(String[] args) {
final VolatileTest1 volatileTest1 = new VolatileTest1();
for(int i = 0; i < 10; i ++){
new Thread(new Runnable() {
public void run() {
volatileTest1.increment();
}
}).start();
}
}
}
线程每次对value进行自增操做,显然输出结果不是咱们想要的那种,这里就出现了线程安全问题,为何?
像value ++这样的操做并不具备原子性,其实际的过程以下:
当线程1在步骤2对value进行计算时,恰好其余线程也对value进行了修改,这时线程1返回的值就不是咱们指望的值了,因而出现线程安全问题,因此volatile不能保证复合操做具备原子性;解决办法就是给increment方法加锁(lock/synchronized)或将变量声明为原子类类型。
Synchronized与volatile区别
1.volatile只能修饰变量,而synchronized能够修改变量,方法以及代码块
2.volatile在多线程中不会存在阻塞问题,synchronized会存在阻塞问题
3.volatile能保证数据的可见性,但不能彻底保证数据的原子性,synchronized即保证了数据的可见性也保证了原子性
4.volatile解决的是变量在多个线程之间的可见性,而sychroized解决的是多个线程之间访问资源的同步性