本篇文章主要讨论的关键字是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,如需转载请自行联系原作者