volatile和synchronized的区别

类别:java  阅读:312  发布时间:Sun Dec 17 21:10:53 CST 2017

    用 volatile 修饰的变量可以保证线程的“可见性”,也就是,任何线程修改了这个 volatile 修饰的值都会通知其他线程来主缓存中重新读取值。通过下面例子说明。

package com.notejava.concurrent.test;

import java.util.concurrent.TimeUnit;

/**
 * Created by liangyaoren on 2017/12/17.
 */
public class VolatileTest {
    //对比有无 volatile 关键字的区别
    volatile boolean running = true;

    public void m(){
        System.out.println("start run");
        while (running){

        }
        System.out.println("end run");
    }

    public static void main(String[] args) {
        VolatileTest volatileTest = new VolatileTest();
        new Thread(volatileTest::m, "t1").start();
        try {
        //主线程睡眠一秒钟,目的是保证t1获得cpu执行权
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        volatileTest.running = false;
    }
}

有volatile的情况下,程序会输出:
start run
end run
无volatile的情况下,程序会输出:
start run

    在有volatile的情况下,程序每次使用 running 变量时都会读取一遍主内存中的 running 并更新当前线程的 running,所以当 running = false 的时候,会跳出循环,输出 end run。

    线程修改一个全局变量要经过三个过程:

① 拷贝主内存的变量到线程内存。

② 修改拷贝的变量值。

③ 把修改的值写回主内存。

    如下图所示:

未命名文件.png

    由于线程修改一个公共变量需要这三个步骤,可见这个修改不具备“原子性”,这也就是volatile和synchronized的不同之处,volatile不能取代synchronized来使用。如下面例子所示:

package com.notejava.concurrent.test;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by liangyaoren on 2017/12/18.
 */
public class VolatileTest2 {
    volatile int count = 0;
    public /*synchronized*/ void m(){
        for (int i =0; i<10000; i++){
            count ++;
        }
    }

    public static void main(String[] args) {
        VolatileTest2 t = new VolatileTest2();
        List threads = new ArrayList<>();
        for (int i = 0; i thread.start());
        threads.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }
}

用volatile的话输出:
91051
而用synchronized的话输出:
100000

    出现问题的代码在 count++,因为 count++不是一个原子操作,从主内存读取 count 回来的时候,在还没来得及修改值,可能主内存的count值已经被其他线程修改过了。    

    上面例子还可以使用AtomicInteger.getAndIncrement(),对于简单的基本数据类型操作,Atomicxxx类比synchronized效率还要高。

关键字:volatile

© copyright 粤ICP备16108162号-1