Java AWT drawImage竞争条件 - 如何使用synchronized来避免它

时间:2011-02-10 00:53:45

标签: java asynchronous awt synchronized doublebuffered

经过数小时的调试和分析,我终于设法找出了竞争条件的原因。解决它是另一回事!

为了查看竞争条件,我在调试过程中录制了一段视频。从那以后,我对这种情况有了进一步的了解,所以请原谅作为调试过程一部分实施的可怜的评论和愚蠢的机制。

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链接,用plat / AWT替换core / Surface.java)。 java)paint / update覆盖,从后备缓冲区到屏幕也是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()通过paint / update的blit,但发生秒。

所以... blit已经同步了。防止竞争状态的明显方法不起作用。 :(

到目前为止,我已经提出了两种解决方案,但它们都不是理想的:

  1. 在下一个渲染过程中重新加上bl 缺点:性能命中,在遇到竞争条件时仍然会有一些闪烁(有效屏幕 - >无效屏幕 - >有效屏幕)

  2. 不要在paint / update上进行blit,而是设置刷新边界并在下一次渲染过程中使用这些边界
    缺点:当屏幕无效且主应用程序线程正在赶上时,会出现黑色闪烁

  3. 这里(1)似乎是两个邪恶中较小的一个。 编辑:和(2)不起作用,获得空白屏幕......(1)工作正常,但只是掩盖了可能仍存在的问题。

    由于我对同步以及如何使用它的理解不足,我希望能找到并且似乎无法想象,是一种锁定机制,它以某种方式解释了drawImage()的异步性质。

    或者也许使用ImageObserver?

    请注意,由于应用程序的性质(Vexi,对于那些感兴趣的人,网站已经过时,我只能使用2个超链接),渲染线程必须在paint / update之外 - 它有一个单线程脚本模型和布局过程(渲染的子过程)调用脚本。

2 个答案:

答案 0 :(得分:0)

不确定,但如果你在AWT绘画线程中Blit会发生什么?

答案 1 :(得分:0)

更新:这里的好方法:AWT custom rendering - capture smooth resizes and eliminate resize flicker


这里的答案是删除paint()线程中的所有blitting,即只从程序线程中的后备缓冲区刷新。这与Jochen Bedersdorfer建议的答案相反,但他的回答永远不会对我们有用,因为该程序有自己的脚本模型,它与驱动渲染的布局模型集成,因此它必须按顺序发生。

(推测)一些问题源于Java加速图形芯片组的不那么出色的多显示器支持,因为我在适应使用BufferStrategy时遇到了this problem,这是一个direct3d + Java差异。

基本上paint()update()会缩减为阻止来电。这样做效果更好,但有一个缺点 - 没有平滑的大小调整。

private class InnerFrame extends Frame() {
    public void update(Graphics g) { }
    public void paint(Graphics g) { }
    ....
}

我最终使用缓冲策略,虽然我对这种方法并不是100%满意,因为在我看来渲染图像效率低,然后将完整图像复制到BufferStrategy然后执行{ {1}}屏幕。

我也实现了基于Swing的替代方案,但我不是特别喜欢它。它使用带有ImageIcon的JLabel,程序线程(不是EDT)将绘制到由ImageIcon包装的Image。

我确信有一个跟进问题让我问我什么时候有更多时间来研究这个目的,但是现在我有两个工作实现,或多或少地解决了这里发布的初始问题 - 以及我学会了很多发现它们的人。