经过数小时的调试和分析,我终于设法找出了竞态条件的原因。解决它是另一回事!
为了查看实际的竞态条件,我在调试过程中录制了一段视频。从那以后,我加深了对这种情况的理解,因此请原谅糟糕的评论和作为调试过程的一部分实施的愚蠢机制。
http://screencast.com/t/aTAk1NOVanjR
因此,情况是:我们有一个表面的双缓冲实现(即 java.awt.Frame 或 Window),其中有一个正在进行的线程基本上连续循环,调用渲染进程(执行 UI 布局并将其渲染到后台缓冲区) ) 然后,在渲染后,将渲染区域从后缓冲区传送到屏幕。
这是双缓冲渲染的伪代码版本(Surface.java的完整版本第 824 行):
public RenderedRegions render() {
// pseudo code
RenderedRegions r = super.render();
if (r==null) // nothing rendered
return
for (region in r)
establish max bounds
blit(max bounds)
return r;
}
与任何 AWT 表面实现一样,它还实现(AWT.java 中的第 507 行 -链接限制 :( - 使用 Surface.java 链接,将 core/Surface.java 替换为 plat/AWT.java)绘制/更新覆盖也 blit从后台缓冲区到屏幕:
public void paint(Graphics gr) {
Rectangle r = gr.getClipBounds();
refreshFromBackbuffer(r.x - leftInset, r.y - topInset, r.width, r.height);
}
使用 drawImage() 函数实现了 Blitting(AWT.java 中的第 371 行):
/** synchronized as otherwise it is possible to blit before images have been rendered to the backbuffer */
public synchronized void blit(PixelBuffer s, int sx, int sy, int dx, int dy, int dx2, int dy2) {
discoverInsets();
try {
window.getGraphics().drawImage(((AWTPixelBuffer)s).i,
dx + leftInset, dy + topInset, // destination topleft corner
dx2 + leftInset, dy2 + topInset, // destination bottomright corner
sx, sy, // source topleft corner
sx + (dx2 - dx), sy + (dy2 - dy), // source bottomright corner
null);
} catch (NullPointerException npe) { /* FIXME: handle this gracefully */ }
}
(警告:这是我开始做假设的地方!)
这里的问题似乎是 drawImage 是异步的,并且首先调用来自 refreshBackBuffer() 通过绘制/更新的 blit,但随后发生。
所以... blit 已经同步了。防止竞争条件的明显方法不起作用。:(
到目前为止,我已经提出了两种解决方案,但它们都不是理想的:
在下一个渲染过程中重新 blit
缺点:性能下降,遇到竞争条件时仍然会出现一些闪烁(有效屏幕 -> 无效屏幕 -> 有效屏幕)不要在绘制/更新时进行 blit,而是设置刷新边界并在下一次渲染过程中使用这些
边界
这里(1)似乎是两害相权取其轻。 编辑:并且(2)不起作用,出现空白屏幕......(1)工作正常,但只是掩盖了可能仍然存在的问题。
我所希望的,并且由于我对同步以及如何使用它的理解不足而似乎无法想象的是一种锁定机制,它以某种方式解释了 drawImage() 的异步性质。
或者也许使用 ImageObserver?
请注意,由于应用程序的性质(Vexi,对于那些感兴趣的人,网站已过时,我只能使用 2 个超链接)渲染线程必须在绘制/更新之外 - 它具有单线程脚本模型和布局过程(渲染的子过程)调用脚本。