在线程之间共享对象的最佳方法?

时间:2016-02-19 09:44:39

标签: java multithreading swing thread-safety

我有一个主Swing个应用,其中包含类成员BufferedImage lastCapturedImageScheduledExecutorService 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更新图像之前完全复制图像。我这样做了吗?

提前致谢!

2 个答案:

答案 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()读取图像以获取当前图像。