Volatile实践

来源:互联网 发布:asp编程语言 编辑:程序博客网 时间:2024/06/05 18:02

  接着上一篇文章”Java理论与实践:正确使用volatile变量“,因为文章中的代码都是片段代码,无法实践,所以看的似懂非懂;所以对上文中“模式#1:状态标志”和“模式#2:一次性安全发布”进行了代码实践,分如下几部分讲解:
- Volatile之Java内存模型概念(参考上一篇文章)
- volatile的作用
- 代码实践及问题说明
- 关于“Java理论与实践:正确使用volatile变量”一文中的疑惑?

1.volatile关键字的两层语义

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
(1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
(2)禁止进行指令重排序。

2.代码实践

2.1模式#1实践

public class Worker {    private boolean stopThread = false;    private WorkThread workThread;    public Worker() {        workThread = new WorkThread();    }    public void start() {        stopThread = false;        workThread.start();    }    public void stop() {        stopThread = true;    }    public class WorkThread extends Thread {        private long i = 0;        @Override        public void run() {            super.run();            System.out.println("start........");            while (!stopThread) {                i++;            }            System.out.println("end........");        }    }} 
public class VolatileTest {    public static void main(String[] args) {        Worker worker = new Worker();        worker.start();        try {            TimeUnit.SECONDS.sleep(2);        } catch (InterruptedException e) {            e.printStackTrace();        }        worker.stop();    }}

(1)测试一
运行上面的main方法,打印结果如下:
start……..
字符串”end……..”并未输出,也就是说WorkThread线程并没有停止运行。
(2)测试二
将Worker类中第2行添加volatile修饰符,修改为如下代码:
private volatile boolean stopThread = false;
然后再次运行main方法,打印结果如下:
start……..
end……..
说明添加了volatile修饰符后,WorkThread线程能够按照预期正常结束了。

对比测试1和测试2说明,WorkThread线程使用的变量stopThread 的值为线程栈中保存的变量副本,main线程修改的值为主存中的值,并未影响WorkThread线程栈中保存的变量副本的值,所以线程没有停止;volatile变量的作用就是告诉WorkThread线程栈每次都直接从主存中获取stopThread 的值,这样stopThread 被volatile修饰后,线程就可以按照预期退出了。
(3)测试三
- stopThread 不使用volatile进行修饰;
- 修改run中的代码,添加System.out.println打印i的值;

@Override        public void run() {            super.run();            System.out.println("start........");            while (!stopThread) {                System.out.println(i);                i++;            }            System.out.println("end........");        }

然后再次运行main方法,打印结果如下:
start……..
end……..
此时,未添加volatile修饰符,WorkThread线程能够按照预期正常结束了。这是为什么呢?

其根本原因是因为,System.out.println方法导致main线程与WorkThread线程产生了变量的同步,也就是WorkThread线程会重新从主存中加载stopThread 到线程栈中。
注:线程的休眠TimeUnit.SECONDS.sleep(2)也会产生同样的效果。

2.2模式#2实践

java单例的通常写法如下

public class TestInstance {    private volatile static TestInstance instance;    public static TestInstance getInstance() { //1        if (instance == null) {                  //2            synchronized (TestInstance.class) {//3                if (instance == null) { //4                    instance = new TestInstance();//5                }            }        }        return instance;//6    }}

在并发情况下,如果没有volatile关键字,在第5行会出现问题
第5行可以分解为3行伪代码

1 memory=allocate();// 分配内存 相当于c的malloc2 ctorInstanc(memory) //初始化对象3 instance=memory //设置instance指向刚分配的地址

上面的代码在编译器运行时,可能会出现重排序,因此其执行顺序不一定是1-2-3 而是1-3-2
如此在多线程下就会出现问题
例如现在有2个线程A,B
线程A在执行第5行代码时,B线程进来,而此时A执行了 1和3,没有执行2,此时B线程判断instance不为null 直接返回一个未初始化的对象,就会出现问题
而用了volatile,上面的重排序就会在多线程环境中禁止,不会出现上述问题。

3. 疑惑

“Java理论与实践:正确使用volatile变量”一文中提到了5种模式,剩下的2种没能理解:
- 独立观察
- “volatile bean”模式
确实没能理解,也不知从何实践,如果遇大神请指点一二,感谢。