我有一个主Swing
个应用,其中包含类成员BufferedImage lastCapturedImage
,ScheduledExecutorService executor
,在线程池中有2个线程。
BufferedImage lastCapturedImage;
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
...
executor.scheduleWithFixedDelay(imageCaptureRunnable, 100, 1000 / TARGET_FPS, TimeUnit.MILLISECONDS);
executor.schedule(roboticArmRunnable, 500, TimeUnit.MILLISECONDS);
第一个Runnable
从网络摄像头中提取图像(BufferedImage
)并更新类实例lastCapturedImage
。
private final Runnable imageCaptureRunnable = new Runnable() {
@Override
public void run() {
lastCapturedImage = webcam.getImage();
}
};
第二个Runnable
处理图像并控制机器人手臂。图像捕获率比机器人手臂消费者快得多,并且机器人手臂消费者仅需要最新图像。如何以线程安全的方式共享图像?
在研究了这个主题后,我的解决方案是将图像(lastCapturedImage
)包装在synchronized
的{{1}}方法的roboticArmRunnable
块中,并制作副本像这样的图像:
run()
我的理解是private final Runnable roboticArmRunnable = new Runnable() {
@Override
public void run() {
while(true){
BufferedImage clonedCameraCapture;
synchronized (lastCapturedImage) {
clonedCameraCapture = copyImage(lastCapturedImage);
}
// process the clonedCameraCapture image and move the robotic arm
}
}
};
块允许synchronized
在允许roboticArmRunnable
更新图像之前完全复制图像。我这样做了吗?
提前致谢!
答案 0 :(得分:4)
不,你的代码不安全。
首先,为了使同步正确,必须同步对共享状态的所有访问,而不仅仅是读访问。
第二:在非final字段上同步是错误的:由于字段可以更改,一个线程将获取旧值的锁定,然后第二个线程将能够进入相同的同步部分,因为该字段已更改
这里没有任何原子性问题要解决:写入和读取引用保证是原子的。您有一个可见性问题需要解决:没有什么能保证图像读取器线程(写入引用)的写入将由机械臂线程(读取引用)可见。
所以你需要的是使字段易变,或将其包装在AtomicReference中:
private volatile BufferedImage lastImage;
或
private AtomicReference<BufferedImage> lastImageRef;
...
// in image reader
lastImageRef.set(theNewImage);
...
// in robotic arm
BufferedImage lastImage = lastImageRef.get();
如果您仍然愿意使用同步解决可见性问题,则必须执行以下操作:
static class LastImageHolder
private BufferedImage lastImage;
public synchronized BufferedImage get() {
return lastImage;
}
public synchronized BufferedImage set(BufferedImage lastImage) {
this.lastImage = lastImage;
}
}
private LastImageHolder lastImageHolder = new LastImageHolder();
答案 1 :(得分:-1)
这样的功能可以在没有锁的情况下完成。这是OneOf
:
class OneOf<T> {
volatile int which = 0;
final T[] them;
public OneOf(T[] them) {
this.them = them;
}
public T get() {
return get(0);
}
public T get(int skip) {
return them[(which + skip) % them.length];
}
public void skip() {
which += 1;
which %= them.length;
}
}
现在,您可以使用get()
获取当前的get(1)
来查看以下内容。在您的情况下,您的图像阅读器将使用get(1)
选择它的图像以开始填充下一个图像,而机器人将使用get()
读取图像以获取当前图像。