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

时间:2021-12-29 20:58:36

I have a main Swing app with class members BufferedImage lastCapturedImage, ScheduledExecutorService executor with 2 threads in the thread pool.

我有一个主Swing应用程序与类成员BufferedImage lastCapturedImage,ScheduledExecutorService执行器在线程池中有2个线程。

BufferedImage lastCapturedImage;
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
...
executor.scheduleWithFixedDelay(imageCaptureRunnable, 100, 1000 / TARGET_FPS, TimeUnit.MILLISECONDS);
executor.schedule(roboticArmRunnable, 500, TimeUnit.MILLISECONDS);

The first Runnable pulls images (BufferedImage) from a webcam and updates a class instance lastCapturedImage.

第一个Runnable从网络摄像头中提取图像(BufferedImage)并更新类实例lastCapturedImage。

private final Runnable imageCaptureRunnable = new Runnable() {

  @Override
  public void run() {
    lastCapturedImage = webcam.getImage();
  }

};

The second Runnable processes the image and controls a robotic arm. The image capturing rate is much faster than the robotic arm consumer and only the most current image is needed by the robotic arm consumer. How best do I share the image in a thread safe way?

第二个Runnable处理图像并控制机器人手臂。图像捕获率比机器人手臂消费者快得多,并且机器人手臂消费者仅需要最新图像。如何以线程安全的方式共享图像?

After researching this topic, my solution is to wrap the image (lastCapturedImage) in a synchronized block in the roboticArmRunnable's run() method, and make a copy of the image like this:

在研究了这个主题之后,我的解决方案是将图像(lastCapturedImage)包装在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
    }
  }
};

My understanding is that the synchronized block will allow for the roboticArmRunnable to completely make the copy of the image before the imageCaptureRunnable is allowed to update the image. Am I doing this right??

我的理解是,在允许imageCaptureRunnable更新图像之前,synchronized块将允许roboticArmRunnable完全复制图像。我这样做对吗?

Thanks in advance!

提前致谢!

2 个解决方案

#1


4  

No, your code isn't safe.

不,你的代码不安全。

First of all, for synchronization to be correct, all the access to the sharedstate must be synchronized, and not just the read access.

首先,为了使同步正确,必须同步对共享状态的所有访问,而不仅仅是读访问。

Second: synchronizing on a non-final field is wrong: since the field can change, one thread will acquire the lock on the old value and the second thread will then be able to enter the same synchronized section because the field has changed.

第二:在非final字段上进行同步是错误的:由于字段可以更改,因此一个线程将获取旧值的锁定,然后第二个线程将能够进入相同的同步部分,因为该字段已更改。

You don't have any atomicity problem to solve here: writing and reading a reference is guaranteed to be atomic. You have a visibility problem to solve though: nothing guarantees that the write made by the image reader thread (that writes the reference) will be visible by the robotic arm thread (that reads the reference).

您在此处没有任何原子性问题:写入和读取引用保证是原子的。您有一个可见性问题需要解决:没有什么能保证图像读取器线程(写入引用)所做的写入将由机械臂线程(读取引用)可见。

So all you need is to make the field volatile, or to wrap it inside an AtomicReference:

因此,您只需要使字段变为volatile,或将其包装在AtomicReference中:

private volatile BufferedImage lastImage;

or

要么

private AtomicReference<BufferedImage> lastImageRef;

...
// in image reader
lastImageRef.set(theNewImage);

...
// in robotic arm
BufferedImage lastImage = lastImageRef.get();

If you were still willing to solve the visibility problem using synchronization, you would have to do something like:

如果您仍然愿意使用同步解决可见性问题,则必须执行以下操作:

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();

#2


-1  

Functionality like this can be done without locks. Here's a OneOf:

这样的功能可以在没有锁的情况下完成。这是一个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;
    }
}

You can now use get() to get the current one, get(1) to peek at the following one. In your case your image reader will choose it's image using get(1) to begin filling the next image while your robot will read the image using get() to get the current one.

您现在可以使用get()获取当前的一个,获取(1)以查看下一个。在您的情况下,您的图像阅读器将使用get(1)选择它的图像以开始填充下一个图像,而机器人将使用get()读取图像以获取当前图像。

#1


4  

No, your code isn't safe.

不,你的代码不安全。

First of all, for synchronization to be correct, all the access to the sharedstate must be synchronized, and not just the read access.

首先,为了使同步正确,必须同步对共享状态的所有访问,而不仅仅是读访问。

Second: synchronizing on a non-final field is wrong: since the field can change, one thread will acquire the lock on the old value and the second thread will then be able to enter the same synchronized section because the field has changed.

第二:在非final字段上进行同步是错误的:由于字段可以更改,因此一个线程将获取旧值的锁定,然后第二个线程将能够进入相同的同步部分,因为该字段已更改。

You don't have any atomicity problem to solve here: writing and reading a reference is guaranteed to be atomic. You have a visibility problem to solve though: nothing guarantees that the write made by the image reader thread (that writes the reference) will be visible by the robotic arm thread (that reads the reference).

您在此处没有任何原子性问题:写入和读取引用保证是原子的。您有一个可见性问题需要解决:没有什么能保证图像读取器线程(写入引用)所做的写入将由机械臂线程(读取引用)可见。

So all you need is to make the field volatile, or to wrap it inside an AtomicReference:

因此,您只需要使字段变为volatile,或将其包装在AtomicReference中:

private volatile BufferedImage lastImage;

or

要么

private AtomicReference<BufferedImage> lastImageRef;

...
// in image reader
lastImageRef.set(theNewImage);

...
// in robotic arm
BufferedImage lastImage = lastImageRef.get();

If you were still willing to solve the visibility problem using synchronization, you would have to do something like:

如果您仍然愿意使用同步解决可见性问题,则必须执行以下操作:

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();

#2


-1  

Functionality like this can be done without locks. Here's a OneOf:

这样的功能可以在没有锁的情况下完成。这是一个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;
    }
}

You can now use get() to get the current one, get(1) to peek at the following one. In your case your image reader will choose it's image using get(1) to begin filling the next image while your robot will read the image using get() to get the current one.

您现在可以使用get()获取当前的一个,获取(1)以查看下一个。在您的情况下,您的图像阅读器将使用get(1)选择它的图像以开始填充下一个图像,而机器人将使用get()读取图像以获取当前图像。