美文网首页
【多线程进阶并发编程二】volatile 应用

【多线程进阶并发编程二】volatile 应用

作者: 5d44bc28b93d | 来源:发表于2018-04-04 16:50 被阅读15次

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的读写,都会将该线程内的本地内存设置为无效,所有的变量都将从主内存中获取。(原理后面会讲到)

相关文章

网友评论

      本文标题:【多线程进阶并发编程二】volatile 应用

      本文链接:https://www.haomeiwen.com/subject/lnvmcftx.html