接着上一篇文章”Java理论与实践:正确使用volatile变量“,因为文章中的代码都是片段代码,无法实践,所以看的似懂非懂;所以对上文中“模式#1:状态标志”和“模式#2:一次性安全发布”进行了代码实践,分如下几部分讲解:
- Volatile之Java内存模型概念(参考上一篇文章)
- volatile的作用
- 代码实践及问题说明
- 关于“Java理论与实践:正确使用volatile变量”一文中的疑惑?
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
(1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
(2)禁止进行指令重排序。
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)也会产生同样的效果。
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,上面的重排序就会在多线程环境中禁止,不会出现上述问题。
“Java理论与实践:正确使用volatile变量”一文中提到了5种模式,剩下的2种没能理解:
- 独立观察
- “volatile bean”模式
确实没能理解,也不知从何实践,如果遇大神请指点一二,感谢。