繁体   English   中英

AtomicReference到可变对象和可见性

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM