Volatile实践

  接着上一篇文章”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的malloc
2 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”模式
确实没能理解,也不知从何实践,如果遇大神请指点一二,感谢。

你可能感兴趣的:(Java并发,volatile,实践,java,可见性,并发)