Для тестирования нам понадобится множество, которое не потребует переизбытка памяти в том случае, если обнаружение проблемы отнимет много времени. Приведенный далее класс CircularSet многократно использует память, в которой хранятся целые числа (int); предполагается, что к тому моменту, когда запись в множество начинается по новому кругу, вероятность конфликта
с перезаписанными значениями минимальна. Методы add() и contains() объявлены как synchronized, чтобы избежать коллизий:
//: concurrency/SerialNumberChecker java // Кажущиеся безопасными операции с появлением потоков // перестают быть таковыми. // {Args- 4}
import java util.concurrent *;
// Reuses storage so we don't run out of memory: class CircularSet {
private int[] array: private int len; private int index = 0; public CircularSet(int size) { array = new int[size], len = size.
// Инициализируем значением, которое не производится // классом SerialNumberGenerator for(int i =0; i < size; i++) array[i] = -1;
}
public synchronized void add(int i) { array[index] = i,
// Возврат индекса к началу с записью поверх старых значений: index = ++index % len.
}
public synchronized boolean contains(int val) { for(int i = 0; i < len; i++)
if(array[i] == val) return true; return false;
public class SerialNumberChecker {
private static final int SIZE = 10; private static CircularSet serials =
new CircularSet(lOOO); private static ExecutorService exec =
Executors.newCachedThreadPool(), static class SerialChecker implements Runnable { public void run() {
while(true) {
int serial =
Seri alNumberGenerator.nextSeri alNumber(); if(serials.contains(serial)) {
System, out. pri ntl nCDuplicate: " + serial); System.exit(O);
}
serials.add(serial);
}
}
}
public static void main(String[] args) throws Exception { for(int i = 0; i < SIZE, i++)
exec, execute (new SerialCheckerO); // Остановиться после n секунд при наличии аргумента:
if(args length > 0) {
TimeUnit SECONDS sleep(new lnteger(args[0])). System out printin("No duplicates detected"), System exit(0).
}
}
} /* Output Duplicate 8468656 *///•-
В классе SerialNumberChecker содержится статическое поле CircuLarSet, хранящее все серийные номера, и вложенный поток Thread, который получает эти номера и удостоверяется в их уникальности. Создав несколько потоков, претендующих на серийные номера, вы обнаружите, что какой-нибудь из них довольно быстро получит уже имеющийся номер (заметьте, что на вашей машине программа может и не обнаружить конфликт, но на многопроцессорной системе она успешно их нашла). Для решения проблемы добавьте к методу nextSe-rialNumber() слово synchronized.
Предполагается, что безопасными атомарными операциями являются чтение и присвоение примитивов. Однако, как мы увидели в программе Atomi-cityTest.java, все так же просто использовать атомарную операцию для объекта, который находится в нестабильном промежуточном состоянии, так что ожидать, что какие-то предположения оправдаются, опасно и ненадежно.
Атомарные классы
В Java SE5 появились специальные классы для выполнения атомарных операций с переменными — Atomiclnteger, AtomicLong, AtomicReference и т. д. Эти классы содержат атомарную операцию условного обновления в форме
boolean compareAndSer(expectedValue, updateValue),
Эти классы предназначены для оптимизации с целью использования атомарности на машинном уровне на некоторых современных процессорах, поэтому в общем случае вам они не понадобятся. Иногда они применяются и в повседневном программировании, но только при оптимизации производительности. Например, версия AtomicityTest.java, переписанная для использования Atomic-Integer, выглядит так:
// concurrency/AtomicIntegerTest java import java.util concurrent *. import java util concurrent atomic *; import java.util.*.
public class AtomicIntegerTest implements Runnable { private Atomiclnteger i = new AtomicInteger(O), public int getValueO { return i getO. } private void evenIncrement() { i addAndGet(2), } public void runО { while(true)
evenlncrement();
}
public static void main(String[] args) {
new TimerO.schedule(new TimerTaskO { public void run() {
System.err println("Aborting"). System exit(O).
}
}, 5000). // Завершение через 5 секунд ExecutorService exec = Executors newCachedThreadPoolO. Atomic I ntegerTest ait = new AtomicIntegerTestO; exec.execute(ait); while(true) {
int val = ait getValueO. if(val % 2 != 0) {
System out.println(val); System.exit(0);
}
}
}
} ///:-
Здесь вместо ключевого слова synchronized используется Atomiclnteger. Так как сбой в программе не происходит, в программу включается таймер, автоматически завершающий ее через 5 секунд.
Вот как выглядит пример MutexEvenGeneratorjava, переписанный для использования класса Atomiclnteger:
//: concurrency/AtomicEvenGenerator.java
// Атомарные классы иногда используются в обычном коде.
// {RunByHand}
import java.util.concurrent.atomic.*;
public class AtomicEvenGenerator extends IntGenerator { private Atomiclnteger currentEvenValue =
new AtomiсInteger(0); public int nextO {
return currentEvenValue.addAndGet(2);
}