摘要

     Volatile是Java提供的一种弱同步机制,当一个变量被声明成volatile类型后编译器不会将该变量的操作与其他内存操作进行重排序。在某些场景下使用volatile代替锁可以减少代码量和使代码更易阅读。
 
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("isRunning的值被修改为为false,线程将被停止了");
}
public static void main(String[] args) throws InterruptedException {
VolatileTest volatileThread = new VolatileTest();
volatileThread.start();
Thread.sleep(1000);
volatileThread.setRunning(false); //停止线程
}
}
输出:
进入了run...............
发现并没有输出”isRunning的值被修改为为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解决的是多个线程之间访问资源的同步性
 
 

Java并发编程--Volatile详解的更多相关文章

  1. Java并发关键字Volatile 详解

    Java并发关键字Volatile 详解 问题引出: 1.Volatile是什么? 2.Volatile有哪些特性? 3.Volatile每个特性的底层实现原理是什么? 相关内容补充: 缓存一致性协议 ...

  2. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  3. java并发编程 | 线程详解

    个人网站:https://chenmingyu.top/concurrent-thread/ 进程与线程 进程:操作系统在运行一个程序的时候就会为其创建一个进程(比如一个java程序),进程是资源分配 ...

  4. Java并发编程(详解wait(), notify(),sleep())

    http://blog.csdn.net/luckyzhoustar/article/details/48179161

  5. Java并发编程 Volatile关键字解析

    volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了 ...

  6. java网络编程(TCP详解)

    网络编程详解-TCP 一,TCP协议的特点              面向连接的协议(有发送端就一定要有接收端)    通过三次连接握手建立连接 通过四次握手断开连接 基于IO流传输数据 传输数据大小 ...

  7. 并发编程之详解InheritableThreadLocal类原理

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] 在Java并发编 ...

  8. Java 并发编程——volatile与synchronized

    一.Java并发基础 多线程的优点 资源利用率更好 程序设计在某些情况下更简单 程序响应更快 这一点可能对于做客户端开发的更加清楚,一般的UI操作都需要开启一个子线程去完成某个任务,否者会容易导致客户 ...

  9. java网络编程(UDP详解)

    UDP详解 一,TCP/IP协议栈中,TCP协议和UDP协议的联系和区别? 联系: TCP和UDP是TCP/IP协议栈中传输层的两个协议,它们使用网络层功能把数据包发送到目的地,从而为应用层提供网络服 ...

随机推荐

  1. jQuery 之$.proxy() 方法

    定义和用法 $.proxy 方法接受一个已有的函数,并返回一个带特定上下文的新的函数. 该方法通常用于向上下文指向不同对象的元素添加事件. 参数 描述 function 要被调用的已有的函数. con ...

  2. hdu1978How many ways (记忆化搜索+DFS)

    Problem Description 这是一个简单的生存游戏,你控制一个机器人从一个棋盘的起始点(1,1)走到棋盘的终点(n,m).游戏的规则描述如下: 1.机器人一开始在棋盘的起始点并有起始点所标 ...

  3. js_day8

  4. 静态方法中不能new内部类的实体对象

    原因如下: 1.内部类可以访问外部类的成员变量 2.对象创建完成后对象的成员变量才会被分配空间 3.main的静态方法执行时可以不存在外部类,不创建实体对象 4.内部类能访问成员变量意味着一定存在外部 ...

  5. Jquery:强大的选择器<一>

    今天回家之后,学习的是Jquery的选择器.选择器作为Jquery的优势之一,确实让我感觉到了它的强大.Jquery选择器分为基本选择器.层次选择器.过滤选择器和表单选择器,下面我一一介绍这四种选择器 ...

  6. Java数据结构漫谈-ArrayList

    ArrayList是一个基于数组实现的链表(List),这一点可以从源码中看出: transient Object[] elementData; // non-private to simplify ...

  7. hdu1087 简单DP

    I - 简单dp 例题扩展 Crawling in process... Crawling failed Time Limit:1000MS     Memory Limit:32768KB     ...

  8. c#鼠标在控件上面,然后显示文字

    先添加toolTip控件到界面 然后每个控件的属性会多一项 ToolTip 第一种:直接给里面加文字 第二种: private void pictureBox_topmost_MouseHover(o ...

  9. No2_2.接口继承多态_Java学习笔记_继承

    ***类的继承***1.继承的实现extends2.[修饰符] class 子类名 extends 父类名{}3.修饰符一般为:public abstract final4.重写是指父子类之间的关系, ...

  10. ecshop安全方面的一些参考建议

    一,ecshop安装,其实很简单,只要一直下一步下一步点击即可,这样总是没有错的,因为官方不可能给我们一个有问题的程序,尽量从简即可. 请注意一下两点 A:在安装ecshop的时候,不要将所有文件都设 ...