java的volatile关键字之非线程安全
来源:互联网 发布:证券软件下载 编辑:程序博客网 时间:2024/06/06 00:31
volatile的不保证原子性
用volatile修饰的变量,线程在每次使用变量的时候,都会读取主存中变量的最新值。volatile不能用来进行原子性操作。
下面看一个例子,我们实现一个计数器,每次线程启动的时候,会调用计数器inc方法,对计数器进行加一
执行环境——jdk版本:jdk1.6.0_31 ,内存 :3G cpu:x86 2.4G
public
class
Counter {
public
static
int
count =
0
;
public
static
void
inc() {
//这里延迟1毫秒,使得结果明显
try
{
Thread.sleep(
1
);
}
catch
(InterruptedException e) {
}
count++;
}
public
static
void
main(String[] args) {
//同时启动1000个线程,去进行i++计算,看看实际结果
for
(
int
i =
0
; i <
1000
; i++) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
Counter.inc();
}
}).start();
}
//这里每次运行的值都有可能不同,可能为1000
System.out.println(
"运行结果:Counter.count="
+ Counter.count);
}
}
运行结果:Counter.count=52
实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count=
995
,可以看出,在多线程的环境下,Counter.count并没有期望结果是
1000
很多人以为,这个是多线程并发问题,只需要在变量count之前加上
volatile
就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望
public
class
Counter {
public
volatile
static
int
count =
0
;
public
static
void
inc() {
//这里延迟1毫秒,使得结果明显
try
{
Thread.sleep(
1
);
}
catch
(InterruptedException e) {
}
count++;
}
public
static
void
main(String[] args) {
//同时启动1000个线程,去进行i++计算,看看实际结果
for
(
int
i =
0
; i <
1000
; i++) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
Counter.inc();
}
}).start();
}
//这里每次运行的值都有可能不同,可能为1000
System.out.println(
"运行结果:Counter.count="
+ Counter.count);
}
}
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,
线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,
在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图
为什么加上了volatile之后,线程A修改了count值,是该所有其他线程中的count值无效了,还不能得到正确结果呢??
为什么加了volatile之后还是线程不安全的呢??
为什么它使用场景要满足那两个条件呢才能保证线程安全呢??
原因都是虽然线程A修改了count值,是该所有其他线程中的count值无效了,其他线程需要从主存中再次读该count值。但是A修改完后的count可能为同步到主存中。所以说A线程修改前count=1,其他线程再次读取的时候count值可能还是1。所以要满足对变量的写操作不依赖于该变量的当前值等条件
描述这写交互
read and load 从主存复制变量到当前工作内存
use and assign 执行代码,改变共享变量值
store and write 用工作内存数据刷新主存相关内容
其中use and assign 可以多次出现
但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的
例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值
在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6
线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6(应该是7)
导致两个线程及时用volatile关键字修改之后,还是在并发的时候出现问题。
运行结果:Counter.count=412
运行结果还是没有我们期望的1000
volatile保证可见性
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
先看一段代码,假如线程1先执行,线程2后执行:
//线程1
boolean
stop =
false
;
while
(!stop){
doSomething();
}
//线程2
stop =
true
;
这段代码是很典型的一段代码,很多人在中断线程时可能都会采用这种标记办法。但是事实上,这段代码会完全运行正确么?即一定会将线程中断么?不一定,也许在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程(虽然这个可能性很小,但是只要一旦发生这种情况就会造成死循环了)。
下面解释一下这段代码为何有可能导致无法中断线程。在前面已经解释过,每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。
那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中(写入主存的操作是在update之后和线程退出之前),线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
但是用volatile修饰之后就变得不一样了:1)修改volatile变量时会强制将修改后的值刷新的主内存中。
2)修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
那么线程1读取到的就是最新的正确的值。volatile的使用场景
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于该变量的当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
下面列举几个Java中使用volatile的几个场景。
1.状态标记量
volatile
boolean
flag =
false
;
while
(!flag){
doSomething();
}
public
void
setFlag() {
flag =
true
;
}
volatile
boolean
inited =
false
;
//线程1:
context = loadContext();
inited =
true
;
//线程2:
while
(!inited ){
sleep()
}
doSomethingwithconfig(context);
2.double check
class
Singleton{
private
volatile
static
Singleton instance =
null
;
private
Singleton() {
}
public
static
Singleton getInstance() {
if
(instance==
null
) {
synchronized
(Singleton.
class
) {
if
(instance==
null
)
instance =
new
Singleton();
}
}
return
instance;
}
}
- java的volatile关键字之非线程安全
- Java线程安全之volatile关键字
- volatile---非线程安全
- java中volatile关键字的含义--volatile并不能做到线程安全
- Volatile关键字与线程安全
- java线程并发处理之Volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Java线程:volatile关键字
- Lua自定义面向对象
- linux命令之系统服务控制练习
- hash取模将大文件转成小文件,可排序,可求TopN
- 项目csdn图片地址
- [UVA 122] Trees on the level 二叉树好难+BFS
- java的volatile关键字之非线程安全
- Add two numbers
- PTA 7-22(排序) 模拟EXCEL排序(25 分)25分代码 结构体排序
- Linux 基础操作(七)————系统服务的控制
- Removed Interval HDU
- (四)系统虚拟化关键技术
- 设备树详解(链接很好,作者嵌入式Linux见解深刻)
- android基本架构
- js数据类型笔记