有人可以解释一下“注意:此方法应该在AWT树锁下调用”吗?

2022-09-04 07:45:46

我试图让我的程序阅读输入到类似问卷的表单中的答案。为此,我计划使用来获取我需要的答案字段(例如文本字段,单选按钮等),然后使用诸如阅读答案之类的方法。getComponents()getText()

我从来没有使用过,只是在学习Java / Swing / AWT。文档中的上述警告吓坏了我,因为我不知道“树锁”是什么,也不知道在哪里可以找到它是什么。谷歌一无所获。getComponents()getComponents()

即使事实证明我的问题的不恰当解决方案,为了学习,我仍然希望我的问题得到解答。getComponents()

谢谢!:)


答案 1

我也对(/)的内部运作感到好奇,所以我做了一些研究,我将尝试在这里总结。AWT tree lockAWTTreeLockTreeLock

首先请记住,在 Swing,只能访问 AWT 事件调度线程 (EDT)(请参阅 Swing 的线程策略)。因此,通常建议始终首选 SwingUtilities#invokeLater() 而不是 GUI 同步任务。如果可以保证对 GUI 组件的所有访问都是在 EDT 上完成的,则通常不需要在 上显式同步 。ComponentsComponent#getTreeLock()Component#getTreeLock()

另一方面,根据David Holmes的说法,AWT最初打算通过多个线程访问,这也可以解释为什么Javadoc的严格AWT类(例如 和 )不要包含通常在 Swing 类的 Javadoc 中找到的线程安全警告(“Swing 不是线程安全的...”)。在这方面,返回所有 AWT 组件共享的锁定对象。AWT 类在修改/访问组件树时(例如在 期间)在此对象上进行同步,当多个线程访问组件树时,客户端负责在此锁上进行同步。从这个角度来看,这似乎是原始AWT设计的遗留物,今天主要是为了向后兼容而存在的,并且可以说是用于某些边界情况,例如在AWT事件调度线程开始之前从多个线程访问组件。另一方面,在某些情况下,树锁上的额外同步也不会受到伤害(我想小的性能损失很容易被忽略) - 例如,我仍然建议在调用期间进行树锁同步(如这些方法的Javadoc中所述),也可能在Java LayoutManagers中的布局操作期间 (正如Javadoc中所建议的 - 实际上这两个规则或多或少是相同的,因为大多数时候都使用这三种方法)。ComponentContainerComponent#getTreeLock()static finalContainer#addImpl()Component#getTreeLock()Container#getComponents()Container#getComponentCount()Container#getComponent(int)Component#getTreeLock()LayoutManagers

有关 AWT 树锁的可用文档和信息

令人遗憾的是,用于GUI线程同步的公共Java API方法的文档记录如此之少。实际上,似乎唯一的“文档”是由Javadoc本身提供的,这很可能被列为不良实践getter文档的一个很好的例子:Component#getTreeLock()

/**
 * Gets this component's locking object (the object that owns the thread
 * synchronization monitor) for AWT component-tree and layout
 * operations.
 * @return this component's locking object
 */

除此之外,在 和 的文档中还有一些关于树锁的预期用法的提示。Container#getComponents()Container#getComponentCount()Container#getComponent(int)

Note: This method should be called under AWT tree lock.

并在一些Java类的源代码注释中引用例如:Component#getTreeLock()java.swing.GroupLayout

public void invalidateLayout(Container parent) {
    checkParent(parent);
    // invalidateLayout is called from Container.invalidate, which
    // does NOT grab the treelock.  All other methods do.  To make sure
    // there aren't any possible threading problems we grab the tree lock
    // here.
    synchronized(parent.getTreeLock()) {
    [...]

这里有一个有趣的细节是,Java在执行布局操作时大部分时间都在树锁上进行同步 - 例如,在方法中,和,和。但奇怪的是,这种模式并没有始终如一地应用于所有Java - 例如 在方法中在树锁上同步,但在方法中不同步,尽管两种方法都访问组件树。 另一方面,在树锁上根本不同步(另请参阅此线程)。FWIW 用于创建自定义布局管理器的 Java 教程根本没有提到树锁,并且给定的示例不执行任何同步。尽管如此,许多第三方也坚持这种同步模式,例如JGoodies FormLayoutCustom Layouts BlogLayoutManagerslayoutContainer()minimumLayoutSize()preferredLayoutSize()FlowLayoutBorderLayoutGridLayoutLayoutManagersGridBagLayoutgetLayoutInfo()arrangeGrid()BoxLayoutLayoutManagers

在容器的 getTreeLock 方法返回的对象上进行同步可确保在执行布局时的线程安全

在我看来,这种不一致和缺乏适当的文档是非常令人困惑的。例如,我仍然不知道在什么情况下绝对有必要在树锁上进行同步。我猜通常(即如果组件访问停留在EDT上)同步应该是不必要的。另一方面,这种额外的同步当然也不会造成伤害......LayoutManagers

此外,Java bug JDK-6784816 提供了有关该主题的一些信息。它指出

AWT 树锁是一种公共锁,应由开发人员(而不是 AWT)用于任何层次结构或布局操作。

并且

在没有AWT树锁的情况下调用它们[方法和]的应用程序必须意识到它们这样做的风险是有风险的。例如,由于某些时序更改,这些方法可能会在 AWT 树锁定下返回不正确的值,而无需正确同步。Container#getComponents()Container#getComponentCount()Container#getComponent(int)

我仍然强烈建议不要仅仅依靠树锁同步而不是在EDT上进行同步,考虑到树锁上的不一致同步可以在Java API中找到(更不用说可能的第三方API)。最后,我将用托马斯·霍廷斯(Thomas Hawtins)关于这个主题的话来结束:

摆动是单线程的,AWT不是。但是,AWT有数千个(字面上)线程错误,这些错误不太可能得到修复。期望多线程 AWT 应用程序代码不会完全损坏是不合理的。


更多参考和链接


答案 2

推荐