[英]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.