本篇文章主要讨论的关键字是volatile.
-
volatile使用场景
-
volatile介绍
-
volatile vs synchronized vs lock
1 volatile使用场景
Volatile的官方定义
Java语言规范第三版中对volatile的定义如下: java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package
com.threadexample.sysn;
public
class
ViolatileTest
extends
Thread {
boolean
keepRunning =
true
;
public
void
run() {
while
(keepRunning) {
//这儿不能进行打印操作,否则会影响演示
//System.out.println("runing--");
}
System.out.println(
"Thread terminated."
);
}
public
static
void
main(String[] args)
throws
InterruptedException {
ViolatileTest t =
new
ViolatileTest();
t.start();
Thread.sleep(
1000
);
t.keepRunning =
false
;
System.out.println(
"keepRunning set to false."
);
}
}
|
这个例子,想达到的效果很简单,main线程休眠1秒,关闭t线程。可运行结果演示,线程t并没有按预期正常退出。
如果使用volatile关键字修饰keepRunning 关键字,可达到预期效果。为什么呢?
2.volatile介绍
volatile的中文意思是“不稳定,反复无常的”。但是,在java领域,被翻译成“可见性”。
在java中,当volatile用于一个作用域时,java保证如下:
2.1 保证有序性
java内存模型,支持指令的重排序。为什么重排序,主要是从性能优化层面考虑的。主要的重排序包括以下:
编译器优化重排序
指令级并行重排序
内存系统重排序
重排序的结果:不会改变执行结果;从多线程角度看,其他线程执行顺序是无序的,从单线程角度看,本线程内的执行顺序是有序的。volatile限制了重排序。volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁,与互斥锁的不同点是:不能像锁一样保证原子性访问。
2.2 保证可见性
根据JMM规定,每个工作线程分别持有本地缓存。线程之间的信息通信,均需要线程的本地缓存与共享缓存进行同步。
volatile声明的变量,不需要保存到本地缓存,也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。
3.volatile vs synchronized vs lock
volatile与synchronized关键字,在功能上有些类似,但也有很多的不同。我个人比较喜欢通过对比的方式来学习,容易加深理解。
上面的演示例子,不使用volatile关键字也是可以的,可以使用同步synchronized。
3.1 使用synchronized,实现定时关闭线程效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
com.threadexample.sysn;
public
class
ViolatileTest
extends
Thread {
private
boolean
keepRunning =
true
;
public
void
run() {
while
(keepRunning) {
System.out.println(
"runing--"
);
}
System.out.println(
"Thread terminated."
);
}
public
synchronized
void
setRunning(
boolean
keepRunning){
this
.keepRunning=keepRunning;
}
public
static
void
main(String[] args)
throws
InterruptedException {
ViolatileTest t =
new
ViolatileTest();
t.start();
Thread.sleep(
1000
);
t.setRunning(
false
);
// t.keepRunning = false;
System.out.println(
"keepRunning set to false."
);
}
}
|
方面 |
volatile | synchronized or lock |
线程安全保障 |
仅仅保证可见性 |
可见性+原子性 |
相对并发性 |
高 |
独占锁,相对低 |
使用场景 |
1.对变量的写入操作,不依赖变量的当前值 2.该变量不会与其他状态变量一起纳入不变性条件中 3.访问变量时,不需要加锁 |
访问共享可变变量时 尽量考虑使用并发容器 |
1.使用violatile保证线程安全例子,这种方式是错误的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package
com.threadexample.sysn;
import
java.util.concurrent.TimeUnit;
public
class
CountTask
implements
Runnable {
private
volatile
int
count;
public
void
run() {
for
(
int
i=
0
;i<
100
;i++){
count++;
try
{
TimeUnit.MILLISECONDS.sleep(
10
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
}
public
int
getCount(){
return
this
.count;}
public
static
void
main(String[] args)
throws
InterruptedException {
CountTask ct =
new
CountTask();
Thread t1 =
new
Thread(ct);
Thread t2 =
new
Thread(ct);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(ct.getCount());
}
}
|
这种方式不是线程安全的,因为存在状态依赖。递增操作,依赖于上一个值。
本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1795257,如需转载请自行联系原作者