java线程技术4_Volatile
2013-03-01 21:25
309 查看
1.Volatile产生原因
Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。这在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。Volatile其实是告诉处理器,
不要将我放入工作内存, 请直接在主存操作我。
volatile的覆盖范围仅仅变量级别的,它的同步代价很低.。Volatile 读操作开销非常低 —— 几乎和非 volatile 读操作一样。而 volatile 写操作的开销要比非 volatile 写操作多很多,因为要保证可见性需要实现内存界定(Memory Fence),即便如此,volatile 的总开销仍然要比锁获取低。
2.Volatile不能使用场景
(1) volatile 变量不能用作线程安全计数器
虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。(如果将值调整为只从单个线程写入,则可以使用)
public class TestRaceCondition {
private volatile int i = 0;
public void increase() {
i++;
}
public int getValue() {
return i;
}
}
当多线程执行increase方法时, 不能保证它的值会是线性递增的。
3.Volatile可以使用场景
使用 volatile 变量替代锁,要始终牢记使用 volatile 的限制
—— 只有在状态真正独立于程序内其他内容时才能使用
volatile
3.1状态标志
volatile 布尔型变量作为状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。很多应用程序包含了一种控制结构,形式为 “在还没有准备好停止程序时再执行一些工作”。
代码:将 volatile 变量作为状态标志使用
/**
* 将 volatile 变量作为状态标志使用.
*
* @version V1.0 ,2011-4-2
* @author xiahui
*
*/
public class VolatileThread1 extends Thread
{
private int count=0;
public static volatile boolean shutdownRequested=false;
public void shutdown(){
shutdownRequested = true;
}
public void run(){
while (!shutdownRequested){
System.out.println(this.getName()+(++count));
try{
sleep(100);
}catch(InterruptedException ex){
}
}
}
public static void main(String[] args) throws Exception
{
VolatileThread1 counter1 = new VolatileThread1();
VolatileThread1 counter2 = new VolatileThread1();
VolatileThread1 counter3 = new VolatileThread1();
counter1.setName("counter1_");
counter2.setName("counter2_");
counter3.setName("c3_");
counter1.start();
counter2.start();
counter3.start();
sleep(500); //
主线程延迟
counter3.shutdown();
System.out.println(Thread.currentThread().getName()+":shutdown");
}
}
运行结果
counter1_1
counter2_1
c3_1
counter1_2
counter2_2
c3_2
counter1_3
counter2_3
c3_3
counter1_4
counter2_4
c3_4
counter1_5
counter2_5
c3_5
main:shutdown
注意:如果变量shutdownRequested不是静态的,则程序只能中止counter3线程,因为每个线程类的变量是私有的。
例2:该类用于
public class VolatileFlag {
int x = 0;
public volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
System.out.println("
value="+x);
}
else
System.out.println("value="+x);
}
}
运行类
public class VolatileThread2 extends Thread {
private int count = 0;
public VolatileFlag flag = null;
public static volatile boolean shutdownRequested=false;
public void shutdown(){
shutdownRequested = true;
}
public void run() {
while (!shutdownRequested) {
System.out.print(this.getName() + (++count));
flag.reader();
try {
sleep(100);
} catch (InterruptedException ex) {
}
}
}
public static void main(String[] args) throws Exception{
VolatileFlag flag=new VolatileFlag();
VolatileThread2 counter1 = new VolatileThread2();
VolatileThread2 counter2 = new VolatileThread2();
VolatileThread2 counter3 = new VolatileThread2();
//三个线程共用一个对象
counter1.flag=flag;
counter2.flag=flag;
counter3.flag=flag;
counter1.setName("counter1_");
counter2.setName("counter2_");
counter3.setName("counter3_");
counter1.start();
counter2.start();
counter3.start();
sleep(3000); //
主线程延迟
flag.writer();//主线程给出写操作
sleep(200); //
主线程延迟
counter3.shutdown();
System.out.println(Thread.currentThread().getName()+":shutdown"+"
value="+flag.x);
}
}
3.2开销较低的读-写锁策略
如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。
public class CheesyCounter {
private volatile int value;
public int getValue() { return value; }
public synchronized int increment() {
return value++;
}
}
使用 synchronized 确保增量操作是原子的 ,使用 volatile 保证当前结果的可见性。
Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。这在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。Volatile其实是告诉处理器,
不要将我放入工作内存, 请直接在主存操作我。
volatile的覆盖范围仅仅变量级别的,它的同步代价很低.。Volatile 读操作开销非常低 —— 几乎和非 volatile 读操作一样。而 volatile 写操作的开销要比非 volatile 写操作多很多,因为要保证可见性需要实现内存界定(Memory Fence),即便如此,volatile 的总开销仍然要比锁获取低。
2.Volatile不能使用场景
(1) volatile 变量不能用作线程安全计数器
虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。(如果将值调整为只从单个线程写入,则可以使用)
public class TestRaceCondition {
private volatile int i = 0;
public void increase() {
i++;
}
public int getValue() {
return i;
}
}
当多线程执行increase方法时, 不能保证它的值会是线性递增的。
3.Volatile可以使用场景
使用 volatile 变量替代锁,要始终牢记使用 volatile 的限制
—— 只有在状态真正独立于程序内其他内容时才能使用
volatile
3.1状态标志
volatile 布尔型变量作为状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。很多应用程序包含了一种控制结构,形式为 “在还没有准备好停止程序时再执行一些工作”。
代码:将 volatile 变量作为状态标志使用
/**
* 将 volatile 变量作为状态标志使用.
*
* @version V1.0 ,2011-4-2
* @author xiahui
*
*/
public class VolatileThread1 extends Thread
{
private int count=0;
public static volatile boolean shutdownRequested=false;
public void shutdown(){
shutdownRequested = true;
}
public void run(){
while (!shutdownRequested){
System.out.println(this.getName()+(++count));
try{
sleep(100);
}catch(InterruptedException ex){
}
}
}
public static void main(String[] args) throws Exception
{
VolatileThread1 counter1 = new VolatileThread1();
VolatileThread1 counter2 = new VolatileThread1();
VolatileThread1 counter3 = new VolatileThread1();
counter1.setName("counter1_");
counter2.setName("counter2_");
counter3.setName("c3_");
counter1.start();
counter2.start();
counter3.start();
sleep(500); //
主线程延迟
counter3.shutdown();
System.out.println(Thread.currentThread().getName()+":shutdown");
}
}
运行结果
counter1_1
counter2_1
c3_1
counter1_2
counter2_2
c3_2
counter1_3
counter2_3
c3_3
counter1_4
counter2_4
c3_4
counter1_5
counter2_5
c3_5
main:shutdown
注意:如果变量shutdownRequested不是静态的,则程序只能中止counter3线程,因为每个线程类的变量是私有的。
例2:该类用于
public class VolatileFlag {
int x = 0;
public volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
System.out.println("
value="+x);
}
else
System.out.println("value="+x);
}
}
运行类
public class VolatileThread2 extends Thread {
private int count = 0;
public VolatileFlag flag = null;
public static volatile boolean shutdownRequested=false;
public void shutdown(){
shutdownRequested = true;
}
public void run() {
while (!shutdownRequested) {
System.out.print(this.getName() + (++count));
flag.reader();
try {
sleep(100);
} catch (InterruptedException ex) {
}
}
}
public static void main(String[] args) throws Exception{
VolatileFlag flag=new VolatileFlag();
VolatileThread2 counter1 = new VolatileThread2();
VolatileThread2 counter2 = new VolatileThread2();
VolatileThread2 counter3 = new VolatileThread2();
//三个线程共用一个对象
counter1.flag=flag;
counter2.flag=flag;
counter3.flag=flag;
counter1.setName("counter1_");
counter2.setName("counter2_");
counter3.setName("counter3_");
counter1.start();
counter2.start();
counter3.start();
sleep(3000); //
主线程延迟
flag.writer();//主线程给出写操作
sleep(200); //
主线程延迟
counter3.shutdown();
System.out.println(Thread.currentThread().getName()+":shutdown"+"
value="+flag.x);
}
}
3.2开销较低的读-写锁策略
如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。
public class CheesyCounter {
private volatile int value;
public int getValue() { return value; }
public synchronized int increment() {
return value++;
}
}
使用 synchronized 确保增量操作是原子的 ,使用 volatile 保证当前结果的可见性。
相关文章推荐
- 【java并发】线程锁技术的使用
- java线程技术1_入门
- 【java并发】传统线程技术中的定时器技术
- Java线程(二):线程同步synchronized和volatile
- Java线程入门学习5----volatile和synchronized关键字
- Java线程技术
- (9)java5的线程【锁lock】与【读写锁_以及模拟缓存(妙用)】技术
- java 线程技术详解
- Java线程(二):线程同步synchronized和volatile
- Java多线程编程核心技术---线程间通信(一)
- java线程技术7_线程中断
- java笔记:熟练掌握线程技术---基础篇之线程的协作和死锁的问题(下)
- Java面向对象 线程技术--上篇
- java线程技术2_线程的创建运行终止
- 【java并发】传统线程技术中的定时器技术
- 【Java线程】volatile的适用场景
- 【java并发】线程技术之死锁问题
- Java线程:volatile关键字
- java5的线程锁技术
- java线程技术6_线程的挂起和唤醒