Статьи

Exchanger и Java без GC

обзор

Класс Exchanger очень эффективен при передаче работы между потоками и переработке используемых объектов. AFAIK, это также один из наименее используемых классов параллелизма.

Тем не менее, если вам не нужен сборщик мусора, использование ArrayBlockingQueue намного проще.

Класс обменника

Класс Exchanger полезен для передачи данных между двумя потоками. например, производитель / потребитель. Он обладает свойством естественной переработки структур данных, используемых для передачи работы, и обеспечивает эффективное совместное использование работы без GC.

Вот пример передачи журналов в фоновый регистратор.

Работа (запись в журнале) группируется в LogEntries и передается в фоновый поток, который позже передает его обратно в поток, чтобы он мог добавить больше работы. При условии, что фоновый поток всегда завершается до заполнения пакета, он почти прозрачен. Увеличение размера пакета уменьшает частоту заполнения пакета, но увеличивает количество необработанных записей, ожидающих одновременно. Вызов flush () может вытолкнуть данные.

Ключевой линией является следующая, которая обменивает партию в текущем потоке с партией в другом потоке. Производитель заполняет партию, а потребитель ее опорожняет.

Обмен, когда это происходит, обычно занимает 1-4 микросекунды. В этом случае один раз каждые 64 строки.

1
entries = logEntriesExchanger.exchange(entries);

Пример обменника

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class BackgroundLogger implements Runnable {
  static final int ENTRIES = 64;
 
  static class LogEntry {
    long time;
    int level;
    final StringBuilder text = new StringBuilder();
  }
 
  static class LogEntries {
    final LogEntry[] lines = new LogEntry[ENTRIES];
    int used = 0;
  }
 
  private final ExecutorService executor = Executors.newSingleThreadExecutor();
  final Exchanger<LogEntries> logEntriesExchanger = new Exchanger<LogEntries>();
  LogEntries entries = new LogEntries();
 
  BackgroundLogger() {
    executor.submit(this);
  }
 
  public StringBuilder log(int level) {
    try {
      if (entries.used == ENTRIES)
        entries = logEntriesExchanger.exchange(entries);
      LogEntry le = entries.lines[entries.used++];
      le.time = System.currentTimeMillis();
      le.level = level;
      return le.text;
 
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
 
  public void flush() throws InterruptedException {
    if(entries.used > 0)
        entries = logEntriesExchanger.exchange(entries);
  }
 
  public void stop() {
    try {
      flush();
    } catch (InterruptedException e) {
      e.printStackTrace(); // use standard logging.
    }
    executor.shutdownNow();
  }
 
  @Override
  public void run() {
    LogEntries entries = new LogEntries();
    try {
      while (!Thread.interrupted()) {
        entries = logEntriesExchanger.exchange(entries);
            for (int i = 0; i < entries.used; i++) {
              bgLog(entries.lines[i]);
              entries.lines[i].text.delete(0, entries.lines[i].text.length());
        }
        entries.used = 0;
      }
    } catch (InterruptedException ignored) {
 
    } finally {
      System.out.println("Warn: logger stopping."); // use standard logging.
    }
  }
 
  private void bgLog(LogEntry line) {
    // log the entry to a file.
  }
}

Справка: Exchanger и Java без GC от нашего партнера JCG Питера Лоури из Vanilla Java .

Статьи по Теме: