为什么volatile使用比synchronized少
在多线程编程中,我们最常用的是synchronized,而对volatile的使用,却相对较少。这一方面是因为volatile的使用场景限制,另一方面是volatile使用需要更高的技术水平。
我们先来看一幅java内存模型图:
每一个线程都有相应的工作内存,工作内存中有一份主内存变量的副本,线程对变量的操作都在工作内存中进行(避免再次访问主内存,提高性能),不同线程不能访问彼此的工作内存,而通过将操作后的值刷新到主内存来进行彼此的交互,这就会带来一个变量值对其他线程的可见性问题。当一个任务在工作内存中变量值进行改变,其他任务对此是不可见的,导致每一个线程都有一份不同的变量副本。而volatile恰恰可以解决这个可见性的问题,当变量被volatile修饰,如private volatile int stateFlag = 0; 它将直接通过主内存中被读取或者写入,线程从主内存中加载的值将是最新的。
但是volatile的使用有着严格的限制,当对变量的操作依赖于以前值(如i++),或者其值被其他字段的值约束,这个时候volatile是无法实现线程安全的。被volatile修饰的变量必须独立于程序的其他状态。因为volatile只是保证了变量的可见性,并不能保证操作的原子性,所谓原子性,即有“不可分”的意思,如对基本数据类型(java中排除long和double)的赋值操作a=6,如返回操作return a,这些操作都不会被线程调度器中断,同一时刻只有一个线程对它进行操作。
看以下代码:
public class VolDemo {
private static volatile int counter;
public static void incNum() {
counter++;
}
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
VolDemo.incNum();
}
});
}
executor.shutdown();
try {
if (!executor.awaitTermination(1000, TimeUnit.MILLISECONDS)) {
System.out.println("some task are not terminated");
} else {
System.out.println("result:" + counter);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
多次运行结果:
result:998
result:988
result:995
......
预期结果应该是1000,尽管counter被volatile修饰,保证了可见性,但是counter++并不是一个原子性操作,它被拆分为读取和写入两部分操作,我们需要用synchronized修饰:
public static synchronized void incNum() {
counter++;
}
此时每次运行结果都是1000,实现了线程安全。synchronized是一种独占锁,它对一段操作或内存进行加锁,当线程要操作被synchronized修饰的内存或操作时,必须首先获得锁才能进行后续操作;但是在同一时刻只能有一个线程获得相同的一把锁,所以它只允许一个线程进行操作。synchronized同样能够将变量最新值刷新到主内存,当一个变量只被synchronized方法操作时,是没有必要用volatile修饰的,所以我们接着把变量声明修改为:
private static int counter;
多次运行结果依旧是1000。
综上所述,由于volatile只能保证变量对多个线程的可见性,但不能保证原子性,它的同步机制是比较脆弱的,它在使用过程中有着诸多限制,对使用者也有更高的要求,相对而言,synchronized锁机制是比较安全的同步机制,有时候出于提高性能的考虑,可以利用volatile对synchronized进行代替和优化,但前提是你必须充分理解其使用场景和涵义。
- 两个四字母域名均以五位数被交易
- Flash/Flex学习笔记(15):FMS 3.5之远程共享对象(Remote Shared Object)
- Android Fragment完全解析
- Centos下堡垒机Jumpserver V3.0环境部署完整记录(2)-配置篇
- Flash/Flex学习笔记(53):利用FMS快速创建一个文本聊天室
- 28家银行用户体验调研报告:洞见银行业的“进化论”
- 性能计数器数据收集服务
- SQL SERVER 内存分配及常见内存问题 DMV查询
- 6 利用Docker .NET应用程序模板制作您的容器应用程序(第2部分)
- Mesos+Zookeeper+Marathon的Docker管理平台部署记录(1)
- git review报错一例
- Nginx采用https加密访问后出现的问题
- 对比git rm和rm的使用区别
- Gerrit日常操作命令收集
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 字节流---复制文件和文件夹
- Jmeter 常用函数(27)- 详解 __env
- 字节流---输入输出
- 常用方法(文件夹操作)
- 常用方法(文件名操作)
- 构建File对象
- 两个常用静态变量
- Spring MVC 整合 Servlet 3.0
- 初探 SpringBoot 自动装配
- 报错405:HTTP method GET is not supported by this URL
- 思科模拟器:网络安全实验
- Django入门:基于 Django 的 Web 页面开发
- 从零开始重新认识 Spring Framework
- 思科模拟器:高级交换实验
- ElasticSearch 基本的查询命令+集成 SpringBoot