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 不能提供必须的原子特性。(如果将值调整为只从单个线程写入,则可以使用)
当多线程执行increase方法时, 不能保证它的值会是线性递增的。
3.Volatile可以使用场景
使用 volatile 变量替代锁,要始终牢记使用 volatile 的限制
—— 只有在状态真正独立于程序内其他内容时才能使用 volatile
3.1状态标志
volatile 布尔型变量作为状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。很多应用程序包含了一种控制结构,形式为 “在还没有准备好停止程序时再执行一些工作”。
代码:将 volatile 变量作为状态标志使用
运行结果
注意:如果变量shutdownRequested不是静态的,则程序只能中止counter3线程,因为每个线程类的变量是私有的。
例2:该类用于
运行类
3.2开销较低的读-写锁策略
如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。
使用 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;
- }
- }
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
例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++;
- }
- }