[英]AtomicReference to a mutable object and visibility
假設我有一個對象列表的AtomicReference
:
AtomicReference<List<?>> batch = new AtomicReference<List<Object>>(new ArrayList<Object>());
線程A將元素添加到此列表: batch.get().add(o);
稍后, 線程B獲取列表,例如,將其存儲在DB: insertBatch(batch.get());
在寫入(線程A)和讀取(線程B)時,是否必須執行其他同步以確保線程B以A的方式查看列表,或者由AtomicReference處理?
換句話說:如果我有一個可變對象的AtomicReference,並且一個線程更改了該對象,其他線程是否會立即看到此更改?
編輯:
也許一些示例代碼是有序的:
public void process(Reader in) throws IOException {
List<Future<AtomicReference<List<Object>>>> tasks = new ArrayList<Future<AtomicReference<List<Object>>>>();
ExecutorService exec = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; ++i) {
tasks.add(exec.submit(new Callable<AtomicReference<List<Object>>>() {
@Override public AtomicReference<List<Object>> call() throws IOException {
final AtomicReference<List<Object>> batch = new AtomicReference<List<Object>>(new ArrayList<Object>(batchSize));
Processor.this.parser.parse(in, new Parser.Handler() {
@Override public void onNewObject(Object event) {
batch.get().add(event);
if (batch.get().size() >= batchSize) {
dao.insertBatch(batch.getAndSet(new ArrayList<Object>(batchSize)));
}
}
});
return batch;
}
}));
}
List<Object> remainingBatches = new ArrayList<Object>();
for (Future<AtomicReference<List<Object>>> task : tasks) {
try {
AtomicReference<List<Object>> remainingBatch = task.get();
remainingBatches.addAll(remainingBatch.get());
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException)cause;
}
throw (RuntimeException)cause;
}
}
// these haven't been flushed yet by the worker threads
if (!remainingBatches.isEmpty()) {
dao.insertBatch(remainingBatches);
}
}
這里發生的是我創建了四個工作線程來解析一些文本(這是process()
方法中的Reader in
參數)。 每個工作程序保存它在批處理中解析的行,並在批處理完成時刷新批處理( dao.insertBatch(batch.getAndSet(new ArrayList<Object>(batchSize)));
)。
由於文本中的行數不是批處理大小的倍數,因此最后一個對象最終會在未刷新的批處理中結束,因為它未滿。 因此,這些剩余的批次由主線程插入。
我使用AtomicReference.getAndSet()
將整個批處理替換為空批處理。 這個程序在線程方面是否正確?
嗯......它真的不像這樣。 AtomicReference
保證引用本身在線程中是可見的,即如果為其指定的引用與原始引用不同,則更新將是可見的。 它不保證引用所指向的對象的實際內容。
因此,對列表內容的讀/寫操作需要單獨同步。
編輯 :因此,根據您更新的代碼和您發布的評論判斷,將本地引用設置為volatile
足以確保可見性。
我想,忘了這里的所有代碼,你確切的問題是:
在寫入(線程A)和讀取(線程B)時,是否必須執行其他同步以確保線程B以A的方式查看列表,或者由AtomicReference處理?
因此,對此的確切響應是: 是的 ,原子能夠照顧可見性。 這不是我的意見,而是JDK文檔之一 :
訪問和更新原子的記憶效應通常遵循揮發性規則,如Java語言規范,第三版(17.4內存模型)中所述。
我希望這有幫助。
添加到都鐸的回答是:你將不得不作出的ArrayList
本身線程或-根據您的要求-即使是較大的代碼塊。
如果你可以使用線程安全的ArrayList
你可以像這樣“裝飾”它:
batch = java.util.Collections.synchronizedList(new ArrayList<Object>());
但請記住:即使像這樣的“簡單”構造也不是線程安全的:
Object o = batch.get(batch.size()-1);
AtomicReference
只會幫助您對列表的引用,它不會對列表本身做任何事情。 更具體地說,在您的方案中,當系統處於負載狀態時,您幾乎肯定會遇到問題,而消費者在生產者向其添加項目時已采用該列表。
這聽起來像你應該使用BlockingQueue
。 如果生產者比消費者更快並且讓隊列處理所有爭用,則可以限制內存占用。
就像是:
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<Object> (50);
// ... Producer
queue.put(o);
// ... Consumer
List<Object> queueContents = new ArrayList<Object> ();
// Grab everything waiting in the queue in one chunk. Should never be more than 50 items.
queue.drainTo(queueContents);
添加
感謝@Tudor指出您正在使用的架構。 ......我不得不承認這很奇怪。 就我所見,你根本不需要AtomicReference
。 每個線程擁有自己的ArrayList
直到它被傳遞給dao
,此時它被替換,因此在任何地方都沒有爭用。
我有點擔心你在一個Reader
上創建四個解析器。 我希望你有辦法確保每個解析器不會影響其他解析器。
我個人會使用某種形式的生產者 - 消費者模式,正如我在上面的代碼中描述的那樣。 或許這樣的事情。
static final int PROCESSES = 4;
static final int batchSize = 10;
public void process(Reader in) throws IOException, InterruptedException {
final List<Future<Void>> tasks = new ArrayList<Future<Void>>();
ExecutorService exec = Executors.newFixedThreadPool(PROCESSES);
// Queue of objects.
final ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<Object> (batchSize * 2);
// The final object to post.
final Object FINISHED = new Object();
// Start the producers.
for (int i = 0; i < PROCESSES; i++) {
tasks.add(exec.submit(new Callable<Void>() {
@Override
public Void call() throws IOException {
Processor.this.parser.parse(in, new Parser.Handler() {
@Override
public void onNewObject(Object event) {
queue.add(event);
}
});
// Post a finished down the queue.
queue.add(FINISHED);
return null;
}
}));
}
// Start the consumer.
tasks.add(exec.submit(new Callable<Void>() {
@Override
public Void call() throws IOException {
List<Object> batch = new ArrayList<Object>(batchSize);
int finishedCount = 0;
// Until all threads finished.
while ( finishedCount < PROCESSES ) {
Object o = queue.take();
if ( o != FINISHED ) {
// Batch them up.
batch.add(o);
if ( batch.size() >= batchSize ) {
dao.insertBatch(batch);
// If insertBatch takes a copy we could merely clear it.
batch = new ArrayList<Object>(batchSize);
}
} else {
// Count the finishes.
finishedCount += 1;
}
}
// Finished! Post any incopmplete batch.
if ( batch.size() > 0 ) {
dao.insertBatch(batch);
}
return null;
}
}));
// Wait for everything to finish.
exec.shutdown();
// Wait until all is done.
boolean finished = false;
do {
try {
// Wait up to 1 second for termination.
finished = exec.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
}
} while (!finished);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.