• Java:多线程中的volatile


    一、为什么使用volatile

    首先,通过一段简单的代码来理解为什么要使用volatile:

     1 public class RunThread extends Thread{
     2     private boolean isRunning = true;
     3     public boolean isRunning(){
     4         return isRunning;
     5     }
     6 
     7     public void setRunning(boolean isRunning){
     8         this.isRunning = isRunning;
     9     }
    10 
    11     @Override
    12     public void run() {
    13         System.out.println("进入run...");
    14         while(isRunning==true){}
    15         System.out.println("线程停止了");
    16     }
    17 }
    18 
    19 
    20 
    21 import java.util.concurrent.TimeUnit;
    22 
    23 public class Run {
    24     public static void main(String[] args){
    25         try{
    26             RunThread thread = new RunThread();
    27             thread.start();
    28             TimeUnit.MILLISECONDS.sleep(1000);
    29             thread.setRunning(false);
    30             System.out.println("赋值为false");
    31         } catch (InterruptedException e) {
    32             e.printStackTrace();
    33         }
    34     }
    35 }

    执行结果:

    我们创建了一个RunThread对象,并启动了该线程。当 isRunning == True时,将一直进行while循环。可以看到,我们启动了线程之后就调用setRunning方法将isRunning设为false。

    按道理来说,当isRunning的状态为false时,就会退出while循环,输出“赋值为false”。然后run方法执行结束,线程执行完毕,整个程序运行结束。

    但通过执行结果发现,线程进入了while死循环,线程从未终止。

    这是由于在启动RunThread.java线程时,变量 private boolean isRunning = True 存在于公共堆栈线程的私有堆栈中。JVM以server模式运行时,为了提高运行效率,线程一直从私有堆栈中读取isRunning的值为True。而thread.setRunning(false)执行后,是把公共堆栈中的isRunning改为false。所以即便将isRunning改为false,程序依然进入了while死循环。

    解决办法:

    使用volatile关键字,把  private boolean isRunning = True 改为 volatile private boolean isRunning = True ,再次运行:

    volatile的作用是:强制线程从公共堆栈中取变量的值,而非从线程的私有堆栈中取值


    二、 volatile的非原子性

    1. 虽然volatile关键字增加了实例变量在多个线程之间的可见性,但它不具备同步性,也就是原子性。

     1 public class MyThread extends Thread{
     2     volatile public static int count;
     3     private static void addCount(){
     4         for(int i =0;i<100;i++){
     5             count++;
     6         }
     7         System.out.println("count= "+count);
     8     }
     9 
    10     @Override
    11     public void run() {
    12         addCount();
    13     }
    14 }
    15 
    16 public class Run {
    17     public static void main(String[] args) {
    18         MyThread[] mythreadArray = new MyThread[100];
    19         for (int i = 0; i < 100; i++) {
    20             mythreadArray[i] = new MyThread();
    21         }
    22         for (int i = 0; i < 100; i++) {
    23             mythreadArray[i].start();
    24         }
    25     }
    26 }

    执行结果:

    volatile保证了线程每次从公共内存中读取变量,保证了同步数据的可见性。但由上述代码可以看出,使用关键字volatile时出现了非线程安全问题。i++ 操作并非一个原子操作,可以分解为:

      (1)从内存读取 i 的值;

      (2)计算 i 的值;

      (3)将 i 的值写入内存

    当一个线程正在执行步骤(2)时,另一个线程也可能也在修改 i 值,就会出现脏数据。

    2. 解决办法

    (1)方法一:使用synchronized关键字

    当在 private static void addCount() 前面加上关键字synchronized后 synchronized private static void addCount() ,再次执行:

    当时用了synchronized关键字时,就不必再使用volatile关键字了。

    (2)方法二:使用原子类进行i++操作

     1 public class MyThread extends Thread{
     2     private static AtomicInteger count = new AtomicInteger(0);
     3     synchronized private static void addCount(){
     4         for(int i =0;i<100;i++){
     5             count.incrementAndGet();
     6         }
     7         System.out.println("count= "+count);
     8     }
     9 
    10     @Override
    11     public void run() {
    12         addCount();
    13     }
    14 }

    注意:原子类并不完全安全,只有原子类的increamentAndGet( )方法是原子的,不保证方法与方法之间的调用也是原子的。

  • 相关阅读:
    项目总结1--技术
    基于MFC的Opengl实现动画
    vs2010 MFC Opengl实现
    设计模式-状态模式
    设计模式-访问者模式
    设计模式-责任链模式
    设计模式-中介者模式
    设计模式-命令模式
    设计模式-备忘录模式
    设计模式-观察者模式
  • 原文地址:https://www.cnblogs.com/huiAlex/p/9037020.html
Copyright © 2020-2023  润新知