使引用变量易失性,是否也会使其所有字段在 java 中也易变?



我读到,使引用变量易失性,不会使其内部字段易变。但是我尝试了以下示例,其中看起来易失性也适用于类的内部字段。

用户.java:- 字段"标志"设置为 true 的用户类。

public class User {
private boolean flag=true;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
} 

MyRunnableThread1.java:-

在这里,我将"用户"设置为易失性,而不是其内部字段"标志"为易失性

子线程在"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");
}
}

线程演示.java:-

在主线程中,我们将"用户"对象的字段"标志"的值设置为 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:-

主线程已启动 子线程:线程 1 已启动 主螺纹完成 子线程:线程 1 已完成

在上面的例子中,我使"user"变量在"MyRunnableThread1.java"类中易变。 用户对象具有字段"flag",该字段在实例化时为 true。 在启动子线程并持续执行循环后,主线程将用户对象的字段"flag"的值更改为false。 但是这里的字段"flag"不是易失性的,而是"user"引用变量是易失性的。但在这里,主线程中字段"flag"的值的变化仍然反映在子线程中。它的行为就好像字段"标志"也是易失性的。 任何人都可以帮助解决上述问题吗?

来自 JLS:

8.3.1.4. 易失性字段

Java 编程语言允许线程访问共享 变量 (§17.1)。通常,确保共享变量 持续可靠地更新,线程应确保它具有 通过获取锁来独占使用此类变量, 通常,对这些共享变量强制执行互斥。

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

字段可以声明为易失性,在这种情况下,Java 内存模型 确保所有线程都能看到变量的一致值 (§17.4)。

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

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

this.user.isFlag()

这样,您的循环包含对易失变量user的读取,并且循环的主体没有优化。

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

例如:

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

不要假定声明引用易失性可以保证引用对象成员的安全发布。

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

当引用对象是不可变的时,声明引用volatile足以保证引用对象成员的安全发布。不能使用该volatile来保证可变对象的安全发布。使用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;
}
} 

最新更新