簡體   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