java.awt.GraphicsConfiguration 是线程安全的吗?有什么选择
Is java.awt.GraphicsConfiguration thread-safe? What are the alternatives
我正在扩展 javax.swing.JComponent
以显示可变数量的图块,它们都具有相同的大小。
如果图块需要新外观,SwingWorker
的 doInBackground()
会为其呈现新的 BufferedImage
。在 done()
中存储图像并调用 JComponent.repaint()
,指示更新区域和预期延迟。被覆盖的JComponent.paintComponent()
会知道该怎么做。
可以通过 GUI 更改图块的大小。很明显,当 SwingWorker
的 StateValue
是 PENDING
或 STARTED
.
时,可能会发生这样的请求
我认为支持cancel()
没有多大意义;它使代码复杂化,并且由于实际渲染不会花费很长时间,因此它的影响将是最小的(如果工作人员不得不等待比它需要执行的时间更长,甚至是有害的)。相反,我想提高效率,如果同一个图块存在 PENDING
,EDT 代码不会启动新的 SwingWorker
。然后,SwingWorker
只需要在 doInBackground()
启动时获取最新设置,并检查它是否真的应该将其结果存储在 done()
.
中
那么SwingWorker
使用的BufferedImage
应该在什么地方铸成呢?这些似乎是选项:
- 预先创建它。缺点:必须选择最大尺寸,因为具体尺寸未知,而且由于
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();
}
...
澄清
看看上面的代码,有人可能会争辩说这个解决方案没有真正的多线程问题,因为 GraphicsConfiguration
对象是在 EDT 上专门为这个特定的工作者创建的。然而,
- 我正在查看抽象 class 实现,它包含静态对象和
- 可能是每次调用
Component.getGraphicsConfiguration()
returns 相同的对象引用。
我认为最安全的方法是从 EDT 上的 GraphicsConfiguration
中提取所有相关信息,将其传递给工作人员,并在那里获得具有合适配置的 new BufferedImage()
。但我在网络上发现了一些提示,表明结果可能会导致 drawImage()
出现令人惊讶的性能下降,这表明可能存在未明确涵盖的配置方面。
采纳 haraldK 的想法,这是一个线程安全的解决方案,我已经在装有 Java SE 1.6.0_26 和 [=39= 的 Linux PC 上进行了测试] 带有 Java SE 1.8.0_40 的 8.1 笔记本。 (显然,代码可以改进,但这超出了这个问答范围。)
在两个平台上,根据处理器速度调整后的性能相当,而且在两个平台上,Transparency.BITMASK
通过 BufferedImage.TYPE_CUSTOM
处理,而 Transparency.OPAQUE
和 Transparency.TRANSLUCENT
使用具体对应 BufferedImage.TYPE_*
值。
同样在两个平台上,使用两个 new BufferedImage()
调用中的任何一个之间没有明显的性能差异,而 GraphicsConfiguration.createCompatibleImage()
肯定慢(30% 到 50%)。
整个机制由内部 class 提供。外部 class extend
s javax.swing.JComponent
所以在那个级别根本没有同步。然而,SwingWorker
s 是匿名的内部 classes 并部署图像创建同步机制。
BufferedImage.getType()
这两个类别的区分在测试平台上似乎没有必要,但谁知道呢
就我而言,内部 class 还包含 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;
}
}
我正在扩展 javax.swing.JComponent
以显示可变数量的图块,它们都具有相同的大小。
如果图块需要新外观,SwingWorker
的 doInBackground()
会为其呈现新的 BufferedImage
。在 done()
中存储图像并调用 JComponent.repaint()
,指示更新区域和预期延迟。被覆盖的JComponent.paintComponent()
会知道该怎么做。
可以通过 GUI 更改图块的大小。很明显,当 SwingWorker
的 StateValue
是 PENDING
或 STARTED
.
我认为支持cancel()
没有多大意义;它使代码复杂化,并且由于实际渲染不会花费很长时间,因此它的影响将是最小的(如果工作人员不得不等待比它需要执行的时间更长,甚至是有害的)。相反,我想提高效率,如果同一个图块存在 PENDING
,EDT 代码不会启动新的 SwingWorker
。然后,SwingWorker
只需要在 doInBackground()
启动时获取最新设置,并检查它是否真的应该将其结果存储在 done()
.
那么SwingWorker
使用的BufferedImage
应该在什么地方铸成呢?这些似乎是选项:
- 预先创建它。缺点:必须选择最大尺寸,因为具体尺寸未知,而且由于
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();
}
...
澄清
看看上面的代码,有人可能会争辩说这个解决方案没有真正的多线程问题,因为 GraphicsConfiguration
对象是在 EDT 上专门为这个特定的工作者创建的。然而,
- 我正在查看抽象 class 实现,它包含静态对象和
- 可能是每次调用
Component.getGraphicsConfiguration()
returns 相同的对象引用。
我认为最安全的方法是从 EDT 上的 GraphicsConfiguration
中提取所有相关信息,将其传递给工作人员,并在那里获得具有合适配置的 new BufferedImage()
。但我在网络上发现了一些提示,表明结果可能会导致 drawImage()
出现令人惊讶的性能下降,这表明可能存在未明确涵盖的配置方面。
采纳 haraldK 的想法,这是一个线程安全的解决方案,我已经在装有 Java SE 1.6.0_26 和 [=39= 的 Linux PC 上进行了测试] 带有 Java SE 1.8.0_40 的 8.1 笔记本。 (显然,代码可以改进,但这超出了这个问答范围。)
在两个平台上,根据处理器速度调整后的性能相当,而且在两个平台上,Transparency.BITMASK
通过 BufferedImage.TYPE_CUSTOM
处理,而 Transparency.OPAQUE
和 Transparency.TRANSLUCENT
使用具体对应 BufferedImage.TYPE_*
值。
同样在两个平台上,使用两个 new BufferedImage()
调用中的任何一个之间没有明显的性能差异,而 GraphicsConfiguration.createCompatibleImage()
肯定慢(30% 到 50%)。
整个机制由内部 class 提供。外部 class extend
s javax.swing.JComponent
所以在那个级别根本没有同步。然而,SwingWorker
s 是匿名的内部 classes 并部署图像创建同步机制。
BufferedImage.getType()
这两个类别的区分在测试平台上似乎没有必要,但谁知道呢
就我而言,内部 class 还包含 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;
}
}