[英]Best way to share object between threads?
I have a main Swing
app with class members BufferedImage lastCapturedImage
, ScheduledExecutorService executor
with 2 threads in the thread pool. 我有一个带有类成员BufferedImage lastCapturedImage
, ScheduledExecutorService executor
程序的主Swing
应用程序,线程池中有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. 第二个Runnable
处理图像并控制机械臂。 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? 如何最好地以线程安全的方式共享映像?
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()
方法中的synchronized
块中,并像这样复制图像:
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. 我的理解是,在允许imageCaptureRunnable
更新图像之前, synchronized
块将允许roboticArmRunnable
完全复制图像。 Am I doing this right?? 我这样做正确吗?
Thanks in advance! 提前致谢!
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. 第二:在非最终字段上进行同步是错误的:由于该字段可以更改,因此一个线程将获得对旧值的锁定,而第二个线程将可以输入相同的同步段,因为该字段已更改。
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: 因此,您需要做的是使字段可变,或将其包装在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();
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. 现在,您可以使用get()
获取当前的内容,而get(1)
可以查看下一个内容。 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()
读取该图像以获取当前图像。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.