使引用变量volatile,在java中使其所有字段也都易变?

时间:2018-05-16 16:23:44

标签: java multithreading volatile

我已经读过将参考变量设置为volatile,不会使其内部字段变得不稳定。但是我尝试使用下面的示例,其中看起来volatile性质也适用于类的内部字段。

User.java:- //将字段“flag”设置为true的用户类。

public class User {

    private boolean flag=true;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
} 

MyRunnableThread1.java: -

这里我将“user”设为volatile,而不是将其内部字段“flag”设为volatile

子线程在“while(this.user.isFlag())”中连续循环。

public class MyRunnableThread1 implements Runnable {

    private String threadName;
    private  volatile User  user; 

    public MyRunnableThread1(String threadName,User user)
    {
        this.threadName=threadName; 
        this.user=user;
    } 

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public void run() {

        System.out.println("child thread:"+threadName+" started");

        while(this.user.isFlag()) {

        }
        System.out.println("child thread:"+threadName+" finished");
    }
}

ThreadDemo.java: -

在主线程中,我们将“User”对象的字段“flag”的值设置为false 终止子线程中的循环

public class ThreadDemo {

    public static void main(final String[] arguments) throws InterruptedException {

        System.out.println("main thread started");

        User user=new User(); 
        MyRunnableThread1 myRunnableThread1=new MyRunnableThread1("Thread1",user);
        Thread t=new Thread(myRunnableThread1);
        t.start();

        try {
            Thread.sleep(6000);
        }
        catch(Exception e) {
            System.out.println("exception in sleep:"+e);
        }

        myRunnableThread1.getUser().setFlag(false);

        System.out.println("main thread finished"); 
    }
}

O / P: -

主线程开始了 子线程:Thread1启动 主线完了 子线程:Thread1完成

在上面的示例中,我将“user”变量设置为“MyRunnableThread1.java”类中的volatile。 用户对象具有字段“flag”,在实例化时为true。 在启动子线程后,它继续执行循环,主线程将用户对象的字段“flag”的值更改为false。 但是这里字段“flag”不是volatile而不是“user”引用变量是volatile。但是仍然在这里主线程中字段“flag”的值的变化反映在子线程中。它表现得好像字段“flag”也是易挥发。 任何人都可以帮助解决上述问题吗?

3 个答案:

答案 0 :(得分:2)

来自JLS:

  

8.3.1.4。 volatile字段

     

Java编程语言允许线程访问共享   变量(第17.1节)。通常,确保共享变量   一致且可靠地更新,线程应确保它具有   通过获得锁定来独占使用这些变量,   通常,对这些共享变量实施互斥。

     

Java编程语言提供了第二种机制,即volatile   字段,这比某些目的的锁定更方便。

     

字段可以声明为volatile,在这种情况下是Java Memory Model   确保所有线程都看到变量的一致值   (§17.4)。

但是对象不是变量。然后在你的情况下,user的值是一致的,这意味着所有线程都看到相同的引用,而不是它们的内部内容观察到相同的值。

答案 1 :(得分:0)

您始终通过对实例的引用来访问成员:

this.user.isFlag()

这样,你的循环包含一个易变变量user的读取,并且循环体没有被优化掉。

尝试在循环之前获取对局部变量的实例引用,并且您将看到差异。

E.g:

    User u = this.user;
    while(u.isFlag())
    {

    }

答案 2 :(得分:0)

不要假设声明引用volatile可以保证引用对象成员的安全发布。

当引用对象是可变的并且缺乏线程安全性时,其他线程可能会看到部分构造的对象或处于不一致状态的对象。

当指示物是不可变的时,声明引用volatile就足以保证指示物成员的安全发布。您不能使用volatile来保证可变对象的安全发布。使用volatile只能保证原始字段,对象引用或不可变对象引用字段的安全发布。

只有在一组有限的情况下才能使用volatile变量而不是lock。对于volatile变量,必须满足以下两个条件才能提供所需的线程安全性:

  • 写入变量不依赖于其当前值。
  • 该变量不参与其他变量的不变量。

要点:

User是可变的,Thread设置flag值并不能保证新值对另一个Thread可见。

选项1:使用volatile flag

public class User {
    private volatile boolean flag = true;
} 

选项2:使用AtomicBoolean

public class User {
    private final AtomicBoolean flag = new AtomicBoolean(true);

    public boolean isFlag() {
        return flag.get();
    }

    public void setFlag(boolean flag) {
        this.flag.set(flag);
    }
} 

选项3:使用synchronized

public class User {
    private boolean flag = true;

    public synchronized boolean isFlag() {
        return flag;
    }

    public synchronized void setFlag(boolean flag) {
        this.flag = flag;
    }
}