volatile特性

volatile保证可见性

   一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1)保证了不同线程对这个变量进行操作的可见性,即一个线程修改了某个变量的值,这新值对其它线程来说是立即可见的

  2)禁止进行指令重排序

 volatile不能确保原子性

 在访问volatile变量时,不会执行加锁操作,因此也就不会执行线程阻塞。所以volatile变量是一种比sychhronized关键字更轻量级的同步机制

 关于这一点,可看个例子:

public class VolatileDemo {

    private volatile int number = 0;

    public int getNumber() {
        return this.number;
    }

    public void add() {
        this.number++;
    }

    public static void main(String[] args) {
        final VolatileDemo demo = new VolatileDemo();
        for (int i = 0; i < 500; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.add();
                }
            }).start();
        }

        // 如果还有子线程在运行,主线程就让出CPU资源
        // 直到所有的子线程都运行完了,主线程再继续往下执行
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println("number:" + demo.getNumber());
    }

}

 大家想一下这段程序的输出结果是多少?也许有些朋友认为是500,但是事实上运行它会发现偶尔会出现小于500的值。如果在number++前,加一个Thread.sleep(),效果更明显

 接下来,我们分析一下为啥会出现这种情况:

 假如某个时刻变量number=5

   1. 线程A读取number的值,然后阻塞

   2. 线程B读取number的值

   3. 线程B执行加1操作

   4. 线程B写入最新的number的值(6)到主内存中。此时线程A工作内存中number还是5

   5. 线程A执行加1操作

   6. 线程A写入最新的number值(6)到主内存中

 两个线程分别进行了一次自增操作后,number只增加了1。

 根源就在自增操作不是原子性操作,而volatile也无法保证对变量的任何操作都是原子性

 解决方案:可以通过synchronizedlock,进行加锁,来保证操作的原子性。也可以通过AtomicInteger

 具体例子:

    public void add2() {
        // 加上synchronized后,就可以保证原子性
        synchronized (this) {
            this.number++;
        }
    }

    public void add3() {
        // 加上lock后,保证原子性
        lock.lock();
        try {
            this.number++;
        } finally {
            lock.unlock();
        }
    }

    //AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减
    private AtomicInteger count = new AtomicInteger(0);

    public void add4() {
        count.getAndIncrement();
    }

 

 volatile的适用场景:

  1、对变量的写入操作不依赖其当前值(如 number++ 、count = count * 5等)

  2、该变量没有包含在具有其它变量的不变式中

       3、volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger

 

 参考资料:http://www.importnew.com/24082.html

 

 

  

posted @ 2018-05-17 15:05  仅此而已-远方  阅读(834)  评论(0编辑  收藏  举报