为什么这段代码不会出现死锁?

3

我正在调查setText的死锁问题,但首先需要学习和了解死锁。为此,我创建了一个简短的程序来尝试复制可能在更大范围内发生的情况,但我不确定为什么我的小程序从未发生死锁。

以下是我的学习程序:

public static void main(String[] a)
{
    JFrame frame = new JFrame();
    final JTextField p = new JTextField("start");

    JButton btn = new JButton("button");
    btn.addActionListener(new ActionListener()
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            SwingUtilities.invokeLater(new Runnable(){
                @Override
                public void run(){
                    p.setText(String.valueOf(System.nanoTime()));
                }
            });
        }
    });

    frame.getContentPane().setLayout(new FlowLayout());
    frame.getContentPane().add(p);
    frame.getContentPane().add(btn);
    frame.setSize(400, 400);
    frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
    frame.setVisible(true);
}

我认为不能在单独的线程中修改Swing,因此我在invokeLater中设置了setText以在按钮点击时更改JTextField。这样做应该会违反单线程规则,这不会导致死锁吗?

3个回答

2
从其他线程对Swing组件进行更改通常不会导致程序死锁,只是JVM没有义务在其他线程中反映出一个线程中所做的状态更改,除非存在happens-before关系,例如synchronized块或访问volatile字段。JVM可能决定仅读取变量的值一次并且在当前线程中永远不会重新读取它,这意味着绘制UI的线程永远不会看到您的更新,或者它可能会在某个不可预测的时间更新它。
使用invokeLater将更新插入到EDT中,确保在setText和下一个绘图操作之间存在happens-before关系。
更新:由于您将Runnable排队的位置发生了变化,现在成功使代码死锁,问题在于当您尝试在其中排队操作时,EDT尚未运行。

为什么在这个问题中,他们在类似于我的设置中遇到了死锁?它们之间有什么区别? - Aequitas
@Aequitas 首先,你没有在 Swing 对象 (框架) 上同步,然后在 Swing 事件线程启动后提交了任务 (另一个线程尝试在任何 Swing 启动之前提交它)。 - chrylis -cautiouslyoptimistic-
我明白了,谢谢。我把invokeLater移到了主函数里而不是按钮按下事件里,但现在它死锁了。你说的同步是什么意思? - Aequitas
这太令人困惑了,我的死锁代码,如果我删除 setLayout 行,它将不再死锁,为什么布局会改变任何东西? - Aequitas

2
在上面的例子中,您正在使用单个线程。Swing作为大多数GUI环境,使用事件队列进行操作。该队列包含必须处理的事物,例如单击事件、文本框编辑事件等。这些都在所谓的GUI线程上执行。Swing不断重绘场景并处理队列中的事件。事件仅在一个线程上处理,这就是为什么在单击处理程序中进行长时间计算(或网络)时应用程序会冻结的原因。当您调用SwingUtilities.invokeLater时,您的代码将被提交并放置在事件队列中。当Swing有一些时间时,它会在GUI线程上执行它。
死锁需要满足以下条件:
  • 至少两个线程
  • 通过首先锁定资源A和B来操作
  • 锁定在不同的线程上以不同的顺序发生
潜在死锁的示例:
Thread1:    Thread2:
lock(A)     lock(B)
lock(B)     lock(A)    <---- may deadlock here
do stuff    do stuff
free(B)     free(A)
free(A)     free(B)

你的示例和你在评论中提供的示例之间的主要区别在于,这里你在主线程上创建GUI(类似于另一个示例),但直到用户单击按钮时才调用Swing的GUI线程。GUI是在主线程上构建的,不会干扰Swing线程。在另一个示例中,GUI是在两个线程中并行构建的。

看看这个问题,他们和我的设置类似,使用invokeLater但仍然遇到了死锁。为什么像你所说的在单线程上会出现这种情况? - Aequitas
更正式地说,死锁必须满足的条件包括:互斥、持续等待、无抢占和循环等待。 - ChiefTwoPencils

0

从不同的线程调用Swing方法确实不明智,但并非因为死锁的风险。主要的风险包括:

  • 线程干扰
  • 内存一致性错误

根据事件分派线程。死锁主要源于多个线程中未正确排序的锁定机制。由于许多Swing对象显然没有适当的锁定,因此死锁不是主要问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接