美文网首页
volatile 关键字

volatile 关键字

作者: AD刘涛 | 来源:发表于2020-03-26 11:02 被阅读0次

volatile 是什么?

  • volatile是一种同步机制,比synchronized或者Lock相关类更轻量,因为使用volatile并不会发生上下文切换等开销很大的行为。
  • 如果一个变量被修饰成volatile,那么JVM就知道了这个变量可能会被并发修改
  • 虽然开销小,相应的能力也小,虽然说volatile是用来同步的保证线程安全的,但是volatile做不到synchronized那样原子保护,volatile仅在很有限的场景下才能发挥作用。

volatile的适用场合?

适用场景一

boolean flag, 如果一个共享变量自始至终只被各个线程赋值,而没有其他的操作,那么就可以用volatile来代替synchronized或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证了可见性,所以就足以保证线程安全。

package jmm;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 描述:     volatile适用的情况1
 */
public class UseVolatile1 implements Runnable {

    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable r =  new UseVolatile1();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(((UseVolatile1) r).done);
        System.out.println(((UseVolatile1) r).realA.get());
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            setDone();
            realA.incrementAndGet();
        }
    }

    private void setDone() {
        done = true;
    }
}

其实这里也适用于int类型,但前提是直接赋值。中间不存在任何其他操作,如对比等。

以下案例线程安全吗?

package jmm;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 描述:     volatile不适用的情况2
 */
public class NoVolatile2 implements Runnable {

    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Runnable r =  new NoVolatile2();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(((NoVolatile2) r).done);
        System.out.println(((NoVolatile2) r).realA.get());
    }
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            flipDone();
            realA.incrementAndGet();
        }
    }

    private void flipDone() {
        done = !done;
    }
}

以上代码多次运行后,我们发现其结果是不可持续的。因为在赋值之前他需要计算之前的bool值,然后再取反。这一过程并非原子操作。

适用场景二:作为刷新之前变量的触发器

volatile的两点作用:

  1. 保证可见性。读一个volatile变量之前,需要先使相应的本地缓存失效,这样就必须到主内存读取最新变量值,写一个volatile属性会立即刷入到主内存。
  2. 禁止指令重排序优化:解决单例双重锁乱序问题。
通过volatile关键字禁止重排序的问题。

如何才能阻止x=0,y=0这样的情况呢?

package jmm;

import java.util.concurrent.CountDownLatch;

/**
 * 描述:禁止重排序的现象(x = 0, y=0) (直到达到 某个条件才停止,测试小概率事件)
 *     
 */
public class OutOfOrderExecution {
    private volatile static int x = 0, y = 0;
    private volatile static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;

            CountDownLatch latch = new CountDownLatch(3);

            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        latch.countDown();
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    a = 1;
                    x = b;  // x = b = 0
                }
            });
            Thread two = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        latch.countDown();
                        latch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    b = 1;
                    y = a; // y = a = 0
                }
            });
            one.start();
            two.start();
            latch.countDown();
            one.join();
            two.join();

            String result = "第" + i + "次(" + x + "," + y + ")";
            if (x == 0 && y == 0) {
                System.out.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}


当我们加入关键字volatile之后,我们发现x=0,y=0这样的情况就不存在了(无论你执行多长时间)。

volatile和synchronized的关系?

volatile在这方面可以看做是轻量版synchronized:如果一个共享变量自始至终只被各个线程赋值,而没有其他的操作,那么就可以用volatile来代替syncronied或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证了可见性,所以就足以保证线程安全。

volatile小结

  1. volatile修饰符适用于以下场景:某个属性被多个线程共享,其中有一个线程修改了此属性,其他线程可以立即得到修改后的值,比如boolean flag;或者作为触发器,实现轻量级同步。
  2. volatile属性的读写操作都是无锁的,他不能替代synchronized,因为他没有提供原子性和互斥性。因为无锁不需要花费时间在获取锁和释放锁上,所以说他是低成本的。
  3. volatile只能作用于属性,我们用volatile修饰符属性,这样compliers就不会对这个属性做指令重排序
  4. volatile 提供了可见性,任何一个线程对其的修改将立马对其他线程可见。volatile属性不会被线程缓存,始终从主存中读取。
  5. volatile提供了happers-before保证,对volatile变量v的写入happers-before所有其他线程后续对v的读操作。
  6. volatile 可以使得long和double的赋值是原子的。

能保证可见性的措施

除了volatile可以让变量保证可见性外,synchronizedLock并发集合Thread.join()Thread.start()都可以保证可见性。具体看happens-before原则规定。

对synchronized可见性的正确理解

  1. synchronized不仅保证了原子性,还保证了可见性
  2. synchronized不仅让保护的代码安全,还近朱者赤

可参考该案例:

package jmm;

/**
 * 描述:     演示可见性带来的问题(通过synchronized来保证可见性)
 */
public class FieldVisibilityABCD {

    int a = 1;
    int b = 2;
    int c = 2;
    int d = 2;

    private void change() {
        a = 3;
        b = 4;
        c = 5;
        synchronized (this) {
            d = 6;
        }
    }


    private void print() {
        synchronized (this) {
            int aa = a;
        }
        int bb = b;
        int cc = c;
        int dd = d;

        System.out.println("b=" + b + ";a=" + a);
    }

    public static void main(String[] args) {
        while (true) {
            FieldVisibilityABCD test = new FieldVisibilityABCD();
            new Thread(() -> {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                test.change();
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }

    }
}

相关文章

网友评论

      本文标题:volatile 关键字

      本文链接:https://www.haomeiwen.com/subject/vzequhtx.html