有人可以解释一下"注意:这个方法应该在AWT树锁下调用."?

Fun*_*ixl 6 java swing awt

我试图让我的程序读出输入问卷形式的答案.为此,我计划使用getComponents()来获取我需要的答案字段(例如文本字段,单选按钮等),然后使用getText()等方法来读取答案.

我从未使用过getComponents()而只是学习Java/Swing/AWT.getComponents()文档中的上述警告吓倒了我,因为我不知道"树锁"是什么,或者在哪里找出它是什么.谷歌一无所获.

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

谢谢!:)

Bal*_*der 15

我也对AWT tree lock(AWTTreeLock/ TreeLock)的内部运作感到好奇,所以我做了一些研究,我将在这里总结一下.

首先考虑到所有的熊,在中摇摆应该访问ComponentsAWT事件指派线程(EDT) (见Swing的线程策略).因此,通常建议始终优先使用SwingUtilities#invokeLater()而不是Component#getTreeLock()GUI同步任务.如果可以保证,对所有GUI组件的访问都是在EDT上完成的,那么通常不需要明确地同步Component#getTreeLock().

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

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

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

/**
 * 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
 */
Run Code Online (Sandbox Code Playgroud)

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

Note: This method should be called under AWT tree lock.
Run Code Online (Sandbox Code Playgroud)

在一些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()) {
    [...]
Run Code Online (Sandbox Code Playgroud)

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

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

在我看来,这种不一致和缺乏适当的文件是非常令人困惑的.例如,我仍然不知道在哪种情况下,绝对需要LayoutManagers在树锁上进行同步.我猜通常(即如果组件访问保留在EDT上),则不需要同步.另一方面,这种额外的同步肯定也不会伤害......

此外,Java bug JDK-6784816提供了有关该主题的一些信息.它说明了这一点

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

然后

调用它们[方法的应用Container#getComponents(),Container#getComponentCount()以及Container#getComponent(int)]没有AWT树锁必须意识到,他们这样做对他们自己的风险.例如,由于某些时序更改,在AWT树锁定下没有正确同步的情况下,方法可能返回不正确的值.

考虑到仅在Java API中可以找到的树锁上的不一致同步(更不用说可能的第三方API),我仍然强烈建议不要仅仅依赖树锁同步而不是EDT上的同步.我将以托马斯·霍滕斯的话就这个问题作出总结:

Swing是单线程的,AWT不是.然而,AWT有数千(字面上)的线程错误,这些错误不太可能被修复.期望多线程AWT应用程序代码不会完全被破坏是不合理的.


进一步的参考和链接