摘要

     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. PHP设计模式笔记八:原型模式 -- Rango韩老师 http://www.imooc.com/learn/236

    原型模式 概述: 1.与工厂模式作用类似,都是用来创建对象 2.与工厂模式的实现不同,原型模式是先创建好一个原型对象,然后通过clone原型对象来创建新的对象,这样就免去了类创建时重复的初始化操作 3 ...

  2. Android : 如何在WebView显示的页面中查找内容

    Android : 如何在WebView显示的页面中查找内容 Author : Aoyousatuo Zhao http://blog.sina.com.cn/aoyousatuo WebView是A ...

  3. JavaScript运算符有哪些

    JavaScript中的运算符有很多,主要分为算术运算符,等同全同运算符,比较运算符,字符串运算符,逻辑运算符,赋值运算符等.这些运算符都有一些属于自己的运算规则,下面就为大家介绍一下JavaScri ...

  4. HDU ACM 1078 FatMouse and Cheese 记忆化+DFS

    题意:FatMouse在一个N*N方格上找吃的,每一个点(x,y)有一些吃的,FatMouse从(0,0)的出发去找吃的.每次最多走k步,他走过的位置能够吃掉吃的.保证吃的数量在0-100.规定他仅仅 ...

  5. linux上网络配置不生效的怪异现象处理

    1.在Linux上.在ifcfg-eth0上设置IP地址等信息 具体配置信息例如以下已 [root@rac01 Desktop]#more/etc/sysconfig/network-scripts/ ...

  6. Unity 3D 动画帧事件

    前几天在项目开发中碰到一个这样的需求,RPG游戏中,特效和动画播放不同步的.假如主角在攻击NPC时,先实例化特效,后播放动画.动画毕竟是有一个时间长度的.等到动画播放攻击挥刀的那一瞬间时,特效可能早就 ...

  7. linux下安装软件的方法

    1. 区分 rpm -qi -qf -ql -qa四个不同选项组合的作用?rpm -qi //查询已经安装的某个RPM软件包的信息rpm -qf //查询某个程序文件是由哪个RPM软件包安装的rpm ...

  8. Mybatis 插入操作时获取主键 (Oracle 触发器与SEQ)

    1.通过Oracle序列 -- Create sequence create sequence SEQ_DW_EWSYSTEM minvalue 1 maxvalue 9999999999999999 ...

  9. HTML与CSS入门——第九章 使用颜色

    知识点: 1.为网站选择颜色的方法 2.颜色在Web上的工作方式 3.使用十六进制颜色值的方法 4.使用CSS设置背景.文本和边框颜色的方法 9.1 选择颜色的最佳方法: 直白地说:根据用户群体找到最 ...

  10. javascript 打开新窗口(window.open)

    打开新窗口(window.open) open() 方法可以查找一个已经存在或者新建的浏览器窗口. 语法: window.open([URL], [窗口名称], [参数字符串]) 参数说明: URL: ...