![](/img/trans.png)
[英]Is it safe to access an object X within a synchronized block defined on object Y?
[英]Assigning a object to a field defined outside a synchronized block - is it thread safe?
這個java代碼的線程安全性有什么問題嗎? 線程1-10通過sample.add()添加數字,而線程11-20調用removeAndDouble()並將結果打印到stdout。 我記得在我的腦海里有人說過,以同樣的方式在removeAndDouble()中使用它來分配項目可能不是線程安全的。 編譯器可以優化指令,使它們不按順序發生。 這是這種情況嗎? 我的removeAndDouble()方法不安全嗎?
從這個代碼的並發角度來看還有什么問題嗎? 我試圖用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.");
}
}
它看起來對我來說是安全的。 這是我的推理。
每次訪問list
時,都會同步進行。 這很棒。 即使您在item
提取list
的一部分,多個線程也不會訪問該item
。
只要你只在同步時訪問list
,你應該是好的(在你當前的設計中。)
您的同步很好,不會導致任何亂序執行問題。
但是,我注意到一些問題。
if you add a list.notifyAll()
after the list.remove(0)
in removeAndDouble
. 首先,如果在removeAndDouble
的list.remove(0)
之后添加list.notifyAll()
,那么waitUntilEmpty
方法會更加 。 這將消除wait(10000)
最多10秒的延遲。
其次,你的list.notify
in add(Integer)
應該是一個notifyAll
,因為notify
只喚醒一個線程,它可能喚醒一個在waitUntilEmpty
內waitUntilEmpty
而不是waitUntilNotEmpty
。
第三,以上都不是你的應用程序的活躍終結,因為你使用了有限的等待,但是如果你做了上面的兩個更改,你的應用程序將有更好的線程性能( waitUntilEmpty
)和有限的等待變得不必要並且可以變得普通老了沒有-arg等待。
您的代碼實際上是線程安全的。 這背后的原因是兩部分。
首先是互斥。 您的同步正確確保一次只有一個線程將修改集合。
第二個問題與您對編譯器重新排序的關注有關。 你擔心編譯實際上可以重新命令它不是線程安全的分配。 在這種情況下你不必擔心它。 在列表上進行同步會創建一個先發生過的關系。 在寫入Integer item
之前,將從列表中刪除所有內容。 這告訴編譯器它無法重新命令該方法中的寫入項。
您的代碼是線程安全的,但不是並發的(並行)。 由於在單個互斥鎖下訪問所有內容,您將序列化所有訪問權限,實際上對結構的訪問是單線程的。
如果您需要生產代碼中描述的功能,則java.util.concurrent
包已經提供了具有(固定大小)數組和(可增長的)基於鏈表的實現的BlockingQueue
。 至少在研究實施思路時,這些非常有趣。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.