java.awt.GraphicsConfiguration是线程安全的吗?有什么选择

时间:2021-10-22 11:47:36

I'm extending javax.swing.JComponent to display a variable number of tiles, which all have the same size.

我正在扩展javax.swing.JComponent来显示可变数量的tile,它们都具有相同的大小。

If a tile needs a new appearance, a SwingWorker's doInBackground() renders a new BufferedImage for it. In done(), the image is stored and JComponent.repaint() is called, indicating the updated area and an intended delay. The overridden JComponent.paintComponent() will know what to do.

如果一个tile需要一个新的外观,SwingWorker的doInBackground()会为它呈现一个新的BufferedImage。在done()中,存储图像并调用JComponent.repaint(),指示更新的区域和预期的延迟。重写的JComponent.paintComponent()将知道该怎么做。

The size of the tiles can be changed via the GUI. Obvioulsy, it could happen that such a request takes place while the the SwingWorker's StateValue is PENDING or STARTED.

可以通过GUI更改切片的大小。很明显,当SwingWorker的StateValue处于PENDING或STARTED状态时,可能会发生这样的请求。

I don't see much sense in supporting cancel(); it complicates the code and since the actual rendering does not take very long, its effect would be minimal (or even harmful if the worker had to wait longer than it will need to execute). Rather, I would like to add efficiency and have the EDT code not start a new SwingWorker if a PENDING one exists for the same tile. Then, the SwingWorker just needs to fetch the latest settings when doInBackground() starts and check whether it should really store its result in done().

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

So where should the BufferedImage used by the SwingWorker be cast into existence? These seem to be the options:

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

  • Create it upfront. Drawbacks: The maximum size must be chosen because the specific size is unknown, and since paintComponent() may run concurrently, two images of maximum size must be kept for all tiles at all times (think ViewPort; a dynamic solution would only require a second image of the actually needed size for visible tiles, temporarily).
  • 事先创建它。缺点:必须选择最大大小,因为特定大小是未知的,并且由于paintComponent()可以同时运行,因此必须始终为所有切片保留两个最大大小的图像(想想ViewPort;动态解决方案只需要一秒钟暂时可见的瓷砖实际所需尺寸的图像。

  • Create it when creating the SwingWorker. Drawback: The maximum size must be provided since it's unknown which size is required once doInBackground() gets fired.
  • 在创建SwingWorker时创建它。缺点:必须提供最大大小,因为一旦doInBackground()被触发,不知道需要哪个大小。

  • Create it in the SwingWOrker. Problem: Given that JComponent.paintComponent() may have to call drawImage() often, it's advisable to use GraphicsConfiguration.createCompatibleImage() to create this image. This may break the single-threadedness limitations of AWT.
  • 在SwingWOrker中创建它。问题:鉴于JComponent.paintComponent()可能必须经常调用drawImage(),建议使用GraphicsConfiguration.createCompatibleImage()来创建此图像。这可能会破坏AWT的单线程限制。

I would prefer the following, but since GraphicsConfiguration belongs to AWT, and the implementation depends on the platform, is this a safe thing to do?

我更喜欢以下,但由于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();
  }
  ...

Clarification

Looking at the above code, one might argue that there is no real muti-threading issue with this solution, since the GraphicsConfiguration object is created on the EDT exclusively for this particular worker. However,

看一下上面的代码,有人可能会说这个解决方案没有真正的多线程问题,因为GraphicsConfiguration对象是在EDT上专门为这个特定的worker创建的。然而,

  • I was looking at the abstract class implementation and it contains static objects and
  • 我在看抽象类实现,它包含静态对象和

  • it might be the case that each call to Component.getGraphicsConfiguration() returns the same object reference.
  • 可能是每次调用Component.getGraphicsConfiguration()都返回相同的对象引用。

I was thinking that the safest approach would be to extract all relevant information from the GraphicsConfiguration on the EDT, pass it to the worker, and get a new BufferedImage() there with the suitable configuration. But I found some hints on the web that the result may lead to a surprising performance hit for drawImage(), suggesting that there might be config aspects which may not be covered explicitly.

我认为最安全的方法是从EDT上的GraphicsConfiguration中提取所有相关信息,将其传递给worker,并在那里获得具有合适配置的新BufferedImage()。但是我在网上发现了一些提示,结果可能会导致drawImage()出现惊人的性能损失,这表明可能存在未明确涵盖的配置方面。

1 个解决方案

#1


Picking up haraldK's ideas, here is a thread-safe solution, which I have tested on a Linux PC with Java SE 1.6.0_26 and a Windows 8.1 notebook with Java SE 1.8.0_40. (Obviously, the code can be improved, buit that's beyond this Q&A.)

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

On both platforms, performance was comparable adjusted for processor speed, and also on both platforms, Transparency.BITMASK was handled via BufferedImage.TYPE_CUSTOM, while Transparency.OPAQUE and Transparency.TRANSLUCENT use specific corresponding BufferedImage.TYPE_* values.

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

Also on both platforms, there was no noticeable performance difference between using any of the two new BufferedImage() calls, while GraphicsConfiguration.createCompatibleImage() was definitely (30% to 50%) slower.

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

The whole mechanism is provided by an inner class. The outer class extends javax.swing.JComponent so there's no synchronisation at all at that level. However, the SwingWorkers are anonymous inner classes and deploy the image creation sync mechanism.

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

The distinction between the two categories of BufferedImage.getType() seems to be unnecessary on the tested platforms, but who knows.

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

In my case, the innter class also contains other information which the SwingWorkers need.

在我的例子中,innter类还包含SwingWorkers需要的其他信息。

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;
  }
}

#1


Picking up haraldK's ideas, here is a thread-safe solution, which I have tested on a Linux PC with Java SE 1.6.0_26 and a Windows 8.1 notebook with Java SE 1.8.0_40. (Obviously, the code can be improved, buit that's beyond this Q&A.)

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

On both platforms, performance was comparable adjusted for processor speed, and also on both platforms, Transparency.BITMASK was handled via BufferedImage.TYPE_CUSTOM, while Transparency.OPAQUE and Transparency.TRANSLUCENT use specific corresponding BufferedImage.TYPE_* values.

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

Also on both platforms, there was no noticeable performance difference between using any of the two new BufferedImage() calls, while GraphicsConfiguration.createCompatibleImage() was definitely (30% to 50%) slower.

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

The whole mechanism is provided by an inner class. The outer class extends javax.swing.JComponent so there's no synchronisation at all at that level. However, the SwingWorkers are anonymous inner classes and deploy the image creation sync mechanism.

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

The distinction between the two categories of BufferedImage.getType() seems to be unnecessary on the tested platforms, but who knows.

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

In my case, the innter class also contains other information which the SwingWorkers need.

在我的例子中,innter类还包含SwingWorkers需要的其他信息。

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;
  }
}