没有看到在单独线程中运行的同一包中的另一个类的静态类变量发生变化



我有一个GUI类,在按下按钮时,将一个值赋给私有静态类变量——我们称它为strX。

类GUI的构造函数也实例化了一个子类(在同一个包中)。这个子类实现了Runnable,并启动了一个线程。在线程中有一个无限循环,它一直查询strX的值,直到它不再为空。查询是通过调用GUI类中的getter方法完成的。

我注意到,即使在我按下按钮并将strX设置为某个非空值之后,子类中的查询也只产生null。

我是Java的新手,我开始怀疑我的理解是否有缺陷。

我读过Java:不能访问来自同一包中的不同类的静态变量及其4个答案,这解决了与变量名称冲突有关的情况。我的情况肯定不是这样。我所要做的就是使用getter方法从子类中获取类变量。

如果我的问题有任何方面需要澄清,请让我知道。我想把代码贴出来,但它太长了,可能会增加混乱。

后续

好的,首先,我要感谢Darren Gilroy提供的详细答案和代码片段。这消除了我头脑中的一些困惑,但我仍然有问题。所以我决定为我的代码创建一个简单的精简版本,并开始测试它。这个简单的GUI类有两个按钮,一个叫做"Only Instantiate",另一个叫做"设置值并实例化"。

  1. 当你push 'Only Instantiate'时,它会创建一个名为PeekAtGUI的另一个类的实例。在PeekAtGUI内部,启动了一个线程。线程调用GUI的静态getter方法获取GUI中静态类变量strX的当前值

  2. 当你按下'Set value and Instantiate'按钮时,它首先将strX的值设置为一个新值,然后实例化PeekAtGUI。

请注意,在调试时,我注释掉了这些实例化中的一个或另一个。

这是底线-当我尝试先实例化,然后按另一个按钮设置新值时,PeekAtGUI似乎没有得到更新的值。然而,如果我用一个按钮来实例化和设置值,那么这一切似乎都神奇地工作了。

不知道发生了什么,任何澄清将是非常感激的。再次感谢。

下面是代码-

类GUI

package intercom;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class GUI {
    private JFrame frame;
    private static volatile String strX = "";
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    GUI window = new GUI();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
/**
 * Create the application.
 */
    public GUI() {
        initialize();
    }
/**
 * Initialize the contents of the frame.
 */
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        frame.getContentPane().add(panel, BorderLayout.CENTER);
        panel.setLayout(null);
        JButton btnSet = new JButton("Set Value and Instantiate");
        btnSet.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setVal(strX+"+");
                new PeekAtGUI();  // When PeekAtGUI instantiated here, it is able to get the new strX
            }
        });
        btnSet.setBounds(106, 94, 224, 23);
        panel.add(btnSet);
        JButton btnStart = new JButton("Only Instantiate");
        btnStart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                //new PeekAtGUI();  // When PeekAtGUI is instantiated here, it is NOT able to get the new strX
            }
        });
        btnStart.setBounds(106, 39, 224, 23);
        panel.add(btnStart);
    }
    public static synchronized void setVal(String str){
        strX = str;
        GUI.class.notifyAll();
    }
    public static synchronized String getVal(){
        return strX;
    }
}
类PeekAtGUI

package intercom;
public class PeekAtGUI implements Runnable{
    private static String msg = "";
    private boolean done = false;
    public PeekAtGUI() {
        Thread r = new Thread(this, "PeekAtGUI Thread");
        r.start();
    }

    public void run() {
        while (done == false){      
            try {getMsg();}catch (InterruptedException e) {e.printStackTrace();}
            System.out.println("Reporting value of strX from PeekAtGUI: " + msg);
            }
    }
    private static synchronized void getMsg() throws InterruptedException{
        while((msg==GUI.getVal())){PeekAtGUI.class.wait();}
        msg = GUI.getVal();
    }
}

在深入尝试跨线程共享数据之前,您需要花一点时间学习Java在线程处理时如何处理各种类型的数据:

即使你通过多个线程访问一个静态值,每个线程都可以有它的本地缓存副本!为了避免这种情况,你可以将变量声明为静态volatile,这将强制线程每次读取全局值。

然而,volatile并不能代替正确的同步!例如…

听起来,您每次都在读取线程的缓存值-您可能会或可能不会看到更改,并且试图使程序可预测地执行将是一个主要的头痛问题。阅读stivlo回答的其余部分,并考虑更合理的线程间通信设计。

作为对后续问题的回应,让我们来看看你的读者是怎么说的……

private static synchronized void getMsg() throws InterruptedException{
    while((msg==GUI.getVal())){PeekAtGUI.class.wait();}
    msg = GUI.getVal();
}

这是你的作者…

public static synchronized void setVal(String str){
    strX = str;
    GUI.class.notifyAll();
}

马上跳出来的问题是,您正在等待一个对象(PeekAtGUI.class),但您正在通知另一个对象(GUI.class)。所以你需要通过让actor在同一个对象上进行通信来解决这个问题。所有的监视器在java中都是公共的,所以你可以只切换一边。(每个对象上的公共监视器不是Java的流行特性!)

题外话:我认为这样做的效果是停放阅读线程。这是你看到的吗?

第二件事是使用==来比较事物。注意,这是引用(指针?)相等,而不是值相等。我不确定价值检查的目的是什么?由于getMsg()已经在循环中,我怀疑您不需要另一个循环来包装等待。除非我遗漏了什么,否则下面的代码可能会起作用:

public void run() {
  while (done == false) { // 'done' should be volatile
    try {
      // you don't need to be in a synchronized block to read
      String msg = GUI.getVal(); 
      System.out.println("Reporting value of strX from PeekAtGUI: " + msg);
      // but you do need lock before you can wait. 
      // It's unusual to lock a class externally in this way, instead
      // one might provide a GUI.waitForNextVal() method.
      synchronized(GUI.class) {
        GUI.class.wait();
      }
    } catch (InterruptedException e) {e.printStackTrace();}
  }
}

感觉你离我越来越近了。这么低级别的工作,我有点生疏了。我们java程序员喜欢我们的抽象!http://www.ibm.com/developerworks/java/library/j-5things4/index.html?ca=drs-

祝你好运。

在java中,线程可以自由地拥有独立于堆的变量的本地副本。这个事实与静态变量和包语义无关。

当考虑java内存模型时,我喜欢认为每个线程都有自己的内存副本,并且只有当您强制它与全局状态同步时,这个副本才会与全局状态同步。

这不是完全准确的,但你永远不会错,只是吸收它将帮助你理解Java内存模型与C/c++的不同之处。

重新描述你的情况:

  1. 你有一个来自一个线程的内存位置设置
  2. 你正在从另一个线程读取内存
  3. Java不保证这是相同的内存位置

在您的例子中,评论者建议在变量中添加volatile。这是而不是与c++的volatile相同。我是说,不完全一样。在java中,对volatile变量的访问使用内存屏障来实现读写器之间的happens-before关系。一种方便的方法是认为对易失性变量的写入将新值"发布"给所有读线程。而对非易失性变量的写操作则不会。

添加这个将修复线程旋转的值b/c,它将立即看到变化。(更准确地说,它会看到变量所持有的引用的变化。)

然而,旋转在Java中是一种不常见的模式,因为Java有大量易于使用的锁(在每个对象上!!)和可靠的等待/通知实现。java程序员可能会这样做:
class Foo {
  private static String value;
  // synchronized keyword on a static method locks Foo.class
  public static synchronized getValue() {
    while(value == null) Foo.class.wait(); // can wait only when you hold the lock
    return value;
  }
  public static synchronized setValue(String v) {
    value = v;
    Foo.class.notifyAll(); // can notify only when you hold the lock
  }
}

"静态同步"方法锁定了Foo.class上的监视器。这实际上是一个不错的介绍:http://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

好运。

相关内容

最新更新