volatile简介
volatile主要作用就是使变量在多线程间可见,理解volatilet特性的最好的方式,就是把volatile的变量的单个读写,看成是使用同一个锁对这些单个读写做了同步操作,因此也称volatile就是轻量的synchronized。
volatile应用
变量在多线程间不可见原因
1522747218(1).png原因就是私有堆栈中的值与公共堆栈中的值不一致所造成的。
证明变量在多线程间不可见
- 错误方案:如果最后线程在不断地输出"打印中。。。"则说明在主线程中修改了flag的值另一个线程是无法可见的
class TestThread extends Thread {
private VolatitleService volatitleService;
TestThread(VolatitleService volatitleService) {
this.volatitleService = volatitleService;
}
@Override
public void run() {
super.run();
volatitleService.printString();
}
}
class VolatitleService {
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void printString() {
while (flag) {
System.out.println("打印中。。。");
}
System.out.println("结束打印");
}
}
public class VolatitleDemo {
public static void main(String[] args) {
VolatitleService volatitleService = new VolatitleService();
TestThread thread = new TestThread(volatitleService);
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatitleService.setFlag(false);
}
}
最后输出(部分)
打印中。。。
打印中。。。
打印中。。。
打印中。。。
打印中。。。
打印中。。。
打印中。。。
结束打印
所以证明了不可见,等等WTF,为什么会这样?别着急都说了是错误方案,我们接着往下看
- 正确方案:在原有的基础上修改VolatitleService,如果一直不输出"结束打印"说明陷入了死循环,说明主线程修改了flag值,但是任务线程并没有发现其修改了
class VolatitleService {
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void printString() {
while (flag) {
}
System.out.println("结束打印");
}
}
输出: 没错就是没有输出,说明确实证明了其不可见。
System.out.print()源码
public void println(boolean x) {
synchronized (this) {
print(x);
newLine();
}
}
结论:方案一与方案二唯一的差别是在while循环中存在System.out.print()方法。此方法内部有synchronized同步块,在线程内执行无论是synchronized还是volatile的读写,都会将该线程内的本地内存设置为无效,所有的变量都将从主内存中获取。所以方案一中也体现了可见性。而方案二,则存在内存不可见。记住这一点很重要。
进一步论证上述观点
class VolatitleService {
private boolean flag = true;
volatile int i = 0;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void printString() {
while (flag) {
i++;
}
System.out.println("结束打印");
System.out.println(flag);
}
}
当我们执行方法是while循环里面进行了volatile的获取以及写入操作,但是线程同样停止了,说明flag也被刷新了。也就证实了这一点。
结论:在线程内执行无论是synchronized还是volatile的读写,都会将该线程内的本地内存设置为无效,所有的变量都将从主内存中获取。
volatile的非原子性
volatile的变量的单个读写是同步的,但是volatile多个操作是不具有原子性的。比如volatile int i的 i++操作,其实这个操作分成三步,
- 从内存中取出i的值
- 计算i的值
- 将i的值写入内存
而这个操作并非原子的。虽然i被volatile修饰。
class MyThread extends Thread {
volatile public static int i = 0;
private static void addI() {
for (int j = 0; j < 100; j++) {
i++;
}
System.out.println(i);
}
@Override
public void run() {
super.run();
addI();
}
}
public class VolatileDemo02 {
public static void main(String[] args) {
MyThread[] myThreads = new MyThread[100];
for(int i=0;i<100;i++){
myThreads[i]= new MyThread();
}
for(int i=0;i<100;i++){
myThreads[i].start();
}
}
}
通过输出可以发现结果运行并不是10000。并没有得到预期的值
使用原子类进行操作
原子操作是不可分割的一个整体,没有其他线程能够中断或者检查正在操作中的原子变量,也就是说他可以在没有锁的情况下实现线程安全。
将前面的例子改成如下方式 发现成功累加到10000
class MyThread extends Thread {
static AtomicInteger i = new AtomicInteger(0);
private static void addI() {
for (int j = 0; j < 100; j++) {
System.out.println(i.incrementAndGet());
}
}
@Override
public void run() {
super.run();
addI();
}
}
原子类未必安全
将上述例子改成下面的方式发现原子操作也未必安全,发现结果是乱序的或者结果不正确。因为原子操作是原子的,但是方法间的调用是非原子的。
class MyThread extends Thread {
static AtomicInteger i = new AtomicInteger(0);
private static void addI() {
for (int j = 0; j < 100; j++) {
System.out.println(i.incrementAndGet());
i.incrementAndGet();
}
}
@Override
public void run() {
super.run();
addI();
}
}
synchronized同样具有volatile同步功能
class VolatitleService {
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
public void printString() {
while (flag) {
synchronized (this){}
}
System.out.println("结束打印");
System.out.println(flag);
}
}
public class VolatitleDemo {
public static void main(String[] args) {
VolatitleService volatitleService = new VolatitleService();
TestThread thread = new TestThread(volatitleService);
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
volatitleService.setFlag(false);
}
}
总结
通过几个简单案例使我们深入的了解volatile。在线程内执行无论是synchronized还是volatile的读写,都会将该线程内的本地内存设置为无效,所有的变量都将从主内存中获取。(原理后面会讲到)
网友评论