java.awt.GraphicsConfiguration是线程安全的吗?有哪些替代方案

时间:2015-05-10 07:29:02

标签: java thread-safety awt bufferedimage swingworker

我正在扩展javax.swing.JComponent以显示可变数量的图块,这些图块都具有相同的尺寸。

如果图块需要新外观,SwingWorker的{​​{1}}会为其呈现新的doInBackground()。在BufferedImage中,存储图像并调用done(),指示更新的区域和预期的延迟。被覆盖的JComponent.repaint()将知道该怎么做。

可以通过GUI更改切片的大小。很明显,在JComponent.paintComponent() SwingWorker StateValuePENDINGSTARTED时会发生此类请求。

我认为支持cancel()没有多大意义;它使代码变得复杂,并且由于实际渲染不需要很长时间,因此它的影响将是最小的(如果工作者必须等待的时间超过它需要执行的时间,则甚至是有害的)。相反,我想提高效率,并且如果同一个磁贴存在SwingWorker,则EDT代码不会启动新的PENDING。然后,SwingWorker只需在doInBackground()启动时获取最新设置,并检查是否应将其结果存储在done()中。

那么BufferedImage使用的SwingWorker应该在哪里存在?这些似乎是选项:

  • 预先创建。缺点:必须选择最大大小,因为特定大小未知,并且由于paintComponent()可能同时运行,因此必须始终为所有切片保留两个最大大小的图像(想想ViewPort;动态解决方案只需要临时的可见瓷砖实际所需尺寸的第二张图像。
  • 创建SwingWorker时创建它。缺点:必须提供最大尺寸,因为在doInBackground()被触发时,不知道需要哪种尺寸。
  • SwingWOrker中创建它。问题:鉴于JComponent.paintComponent()可能需要经常调用drawImage(),建议您使用GraphicsConfiguration.createCompatibleImage()来创建此图片。这可能会破坏AWT的单线程限制。

我更喜欢以下内容,但由于GraphicsConfiguration属于AWT,并且实现取决于平台,这是安全的事情吗?

  ...
  final GraphicsConfiguration gc = this.getGraphicsConfiguration();
  if ((obj.worker == null) ||
      (obj.worker.getState() != SwingWorker.StateValue.PENDING)) {
    obj.worker = new SwingWorker<BufferedImage, Void>() {
        @Override public BufferedImage doInBackground() {
          ... // acquire size info via synchronised access
          final BufferedImage img = gc.createCompatibleImage(...);
          ...
          return img;
        }
        @Override public void done() {
          if (obj.worker == this) {
            obj.worker = null;       
            try   { obj.image = this.get(); }
            catch (Throwable t) { ... System.exit(1); }
            Outer.this.requestTileRepaint(...);
          }
        }
      };
    obj.worker.execute();
  }
  ...

澄清

查看上面的代码,有人可能会认为这个解决方案没有真正的多线程问题,因为{EDDT上的GraphicsConfiguration对象专门为这个特定的工作者创建。然而,

  • 我正在查看抽象类实现,它包含静态对象和
  • 可能是每次调用Component.getGraphicsConfiguration()都会返回相同的对象引用。

我认为最安全的方法是从EDT上的GraphicsConfiguration中提取所有相关信息,将其传递给工作人员,然后使用合适的配置获得new BufferedImage()。但我在网上发现了一些提示,结果可能会导致drawImage()的性能出人意料地受到打击,这表明可能存在未明确涵盖的配置方面。

1 个答案:

答案 0 :(得分:0)

挑选haraldK的想法,这是一个线程安全的解决方案,我在带有Java SE 1.6.0_26的Linux PC和带有Java SE 1.8.0_40的Windows 8.1笔记本上进行了测试。 (显然,代码可以改进,超出此Q&amp; A。)

在这两个平台上,性能均可根据处理器速度进行调整,并且在两个平台上,Transparency.BITMASK均通过BufferedImage.TYPE_CUSTOM处理,而Transparency.OPAQUETransparency.TRANSLUCENT使用特定的相应BufferedImage.TYPE_*值。

同样在这两个平台上,使用两个new BufferedImage()调用中的任何一个都没有明显的性能差异,而GraphicsConfiguration.createCompatibleImage()肯定(30%到50%)更慢。

整个机制由内部类提供。外部类extend s javax.swing.JComponent因此在该级别根本没有同步。但是,SwingWorker是匿名内部类,并部署映像创建同步机制。

在测试平台上,两类BufferedImage.getType()之间的区别似乎是不必要的,但谁知道呢。

在我的情况下,这个内部类还包含SwingWorker需要的其他信息。

private static final class WokerSync
{
  private Object        refImageMutex       = new Object();
  private BufferedImage refImageOpaque      = null;
  private BufferedImage refImageTranspMask  = null;
  private BufferedImage refImageTranslucent = null;

  public void setRefImagesFromEDT(final GraphicsConfiguration grConf) {
    if (grConf != null) {
      synchronized(this.refImageMutex) {
        this.refImageOpaque      = grConf.createCompatibleImage(1, 1, Transparency.OPAQUE);
        this.refImageTranspMask  = grConf.createCompatibleImage(1, 1, Transparency.BITMASK);
        this.refImageTranslucent = grConf.createCompatibleImage(1, 1, Transparency.TRANSLUCENT);
      }
    }
  }
  private BufferedImage getCompatibleImage(final BufferedImage refImage, final int width, final int height) {
    BufferedImage img = null;
    if (refImage != null) {
      final int grType = refImage.getType();
      if (grType == BufferedImage.TYPE_CUSTOM) {
        final ColorModel               cm = refImage.getColorModel();
        final WritableRaster           wr = cm.createCompatibleWritableRaster(width, height);
        final String[]                 ps = refImage.getPropertyNames();
        final int                      pl = (ps == null) ? 0 : ps.length;
        final Hashtable<String,Object> ph = new Hashtable<String,Object>(pl);
        for (int pi=0; pi<pl; pi++) {
          ph.put(ps[pi], refImage.getProperty(ps[pi]));
        }
        img = new BufferedImage(cm, wr, cm.isAlphaPremultiplied(), ph);
      } else {
        img = new BufferedImage(width, height, grType);
      }
    }
    return img;
  }
  public BufferedImage getCompatibleImageOpaque(final int width, final int height) {
    BufferedImage img = null;
    synchronized(this.refImageMutex) {
      img = this.getCompatibleImage(this.refImageOpaque, width, height);
    }
    return img;
  }
  public BufferedImage getCompatibleImageTranspMask(final int width, final int height) {
    BufferedImage img = null;
    synchronized(this.refImageMutex) {
      img = this.getCompatibleImage(this.refImageTranspMask, width, height);
    }
    return img;
  }
  public BufferedImage getCompatibleImageTranslucent(final int width, final int height) {
    BufferedImage img = null;
    synchronized(this.refImageMutex) {
      img = this.getCompatibleImage(this.refImageTranslucent, width, height);
    }
    return img;
  }
}