[英]Assigning a object to a field defined outside a synchronized block - is it thread safe?
Is there anything wrong with the thread safety of this java code? 这个java代码的线程安全性有什么问题吗? Threads 1-10 add numbers via sample.add(), and Threads 11-20 call removeAndDouble() and print the results to stdout.
线程1-10通过sample.add()添加数字,而线程11-20调用removeAndDouble()并将结果打印到stdout。 I recall from the back of my mind that someone said that assigning item in same way as I've got in removeAndDouble() using it outside of the synchronized block may not be thread safe.
我记得在我的脑海里有人说过,以同样的方式在removeAndDouble()中使用它来分配项目可能不是线程安全的。 That the compiler may optimize the instructions away so they occur out of sequence.
编译器可以优化指令,使它们不按顺序发生。 Is that the case here?
这是这种情况吗? Is my removeAndDouble() method unsafe?
我的removeAndDouble()方法不安全吗?
Is there anything else wrong from a concurrency perspective with this code? 从这个代码的并发角度来看还有什么问题吗? I am trying to get a better understanding of concurrency and the memory model with java (1.6 upwards).
我试图用java(1.6向上)更好地理解并发性和内存模型。
import java.util.*;
import java.util.concurrent.*;
public class Sample {
private final List<Integer> list = new ArrayList<Integer>();
public void add(Integer o) {
synchronized (list) {
list.add(o);
list.notify();
}
}
public void waitUntilEmpty() {
synchronized (list) {
while (!list.isEmpty()) {
try {
list.wait(10000);
} catch (InterruptedException ex) { }
}
}
}
public void waitUntilNotEmpty() {
synchronized (list) {
while (list.isEmpty()) {
try {
list.wait(10000);
} catch (InterruptedException ex) { }
}
}
}
public Integer removeAndDouble() {
// item declared outside synchronized block
Integer item;
synchronized (list) {
waitUntilNotEmpty();
item = list.remove(0);
}
// Would this ever be anything but that from list.remove(0)?
return Integer.valueOf(item.intValue() * 2);
}
public static void main(String[] args) {
final Sample sample = new Sample();
for (int i = 0; i < 10; i++) {
Thread t = new Thread() {
public void run() {
while (true) {
System.out.println(getName()+" Found: " + sample.removeAndDouble());
}
}
};
t.setName("Consumer-"+i);
t.setDaemon(true);
t.start();
}
final ExecutorService producers = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int j = i * 10000;
Thread t = new Thread() {
public void run() {
for (int c = 0; c < 1000; c++) {
sample.add(j + c);
}
}
};
t.setName("Producer-"+i);
t.setDaemon(false);
producers.execute(t);
}
producers.shutdown();
try {
producers.awaitTermination(600, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
sample.waitUntilEmpty();
System.out.println("Done.");
}
}
It looks thread safe to me. 它看起来对我来说是安全的。 Here is my reasoning.
这是我的推理。
Everytime you access list
you do it synchronized. 每次访问
list
时,都会同步进行。 This is great. 这很棒。 Even though you pull out a part of the
list
in item
, that item
is not accessed by multiple threads. 即使您在
item
提取list
的一部分,多个线程也不会访问该item
。
As long as you only access list
while synchronized, you should be good (in your current design.) 只要你只在同步时访问
list
,你应该是好的(在你当前的设计中。)
Your synchronization is fine, and will not result in any out-of-order execution problems. 您的同步很好,不会导致任何乱序执行问题。
However, I do notice a few issues. 但是,我注意到一些问题。
First, your waitUntilEmpty
method would be much more timely if you add a list.notifyAll()
after the list.remove(0)
in removeAndDouble
. 首先,如果在
removeAndDouble
的list.remove(0)
之后添加list.notifyAll()
,那么waitUntilEmpty
方法会更加及时 。 This will eliminate an up-to 10 second delay in your wait(10000)
. 这将消除
wait(10000)
最多10秒的延迟。
Second, your list.notify
in add(Integer)
should be a notifyAll
, because notify
only wakes one thread, and it may wake a thread that is waiting inside waitUntilEmpty
instead of waitUntilNotEmpty
. 其次,你的
list.notify
in add(Integer)
应该是一个notifyAll
,因为notify
只唤醒一个线程,它可能唤醒一个在waitUntilEmpty
内waitUntilEmpty
而不是waitUntilNotEmpty
。
Third, none of the above is terminal to your application's liveness, because you used bounded waits, but if you make the two above changes, your application will have better threaded performance ( waitUntilEmpty
) and the bounded waits become unnecessary and can become plain old no-arg waits. 第三,以上都不是你的应用程序的活跃终结,因为你使用了有限的等待,但是如果你做了上面的两个更改,你的应用程序将有更好的线程性能(
waitUntilEmpty
)和有限的等待变得不必要并且可以变得普通老了没有-arg等待。
Your code as-is is in fact thread safe. 您的代码实际上是线程安全的。 The reasoning behind this is two part.
这背后的原因是两部分。
The first is mutual exclusion. 首先是互斥。 Your synchronization correctly ensures that only one thread at a time will modify the collections.
您的同步正确确保一次只有一个线程将修改集合。
The second has to do with your concern about compiler reordering. 第二个问题与您对编译器重新排序的关注有关。 Youre worried that the compile can in fact re order the assigning in which it wouldnt be thread safe.
你担心编译实际上可以重新命令它不是线程安全的分配。 You dont have to worry about it in this case.
在这种情况下你不必担心它。 Synchronizing on the list creates a happens-before relationship.
在列表上进行同步会创建一个先发生过的关系。 All removes from the list happens-before the write to
Integer item
. 在写入
Integer item
之前,将从列表中删除所有内容。 This tells the compiler that it cannot re order the write to item in that method. 这告诉编译器它无法重新命令该方法中的写入项。
Your code is thread-safe, but not concurrent (as in parallel). 您的代码是线程安全的,但不是并发的(并行)。 As everything is accessed under a single mutual exclusion lock, you are serialising all access, in effect access to the structure is single-threaded.
由于在单个互斥锁下访问所有内容,您将序列化所有访问权限,实际上对结构的访问是单线程的。
If you require the functionality as described in your production code, the java.util.concurrent
package already provides a BlockingQueue
with (fixed size) array and (growable) linked list based implementations. 如果您需要生产代码中描述的功能,则
java.util.concurrent
包已经提供了具有(固定大小)数组和(可增长的)基于链表的实现的BlockingQueue
。 These are very interesting to study for implementation ideas at the very least. 至少在研究实施思路时,这些非常有趣。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.