Философия Java3
Шрифт:
Для тестирования нам понадобится множество, которое не потребует переизбытка памяти в том случае, если обнаружение проблемы отнимет много времени. Приведенный далее класс CircularSet многократно использует память, в которой хранятся целые числа (int); предполагается, что к тому моменту, когда запись в множество начинается по новому кругу, вероятность конфликта
с перезаписанными значениями минимальна. Методы add и contains объявлены как synchronized, чтобы избежать коллизий:
//: concurrency/SerialNumberChecker java // Кажущиеся безопасными операции с появлением потоков // перестают быть
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);
}
public static void main(String[] args) {
EvenChecker.test(new AtomicEvenGeneratorO);
}
} ///.-
Стоит еще раз подчеркнуть, что классы Atomic проектировались для построения классов из java.util.concurrent. Используйте их в своих программах только в особых случаях и только тогда, когда вы твердо уверены, что это не создаст новых проблем. В общем случае безопаснее использовать блокировки (с ключевым словом synchronized или явным созданием объектов Lock).
Критические секции
Иногда необходимо предотвратить доступ нескольких потоков только к части кода, а не к методу в целом. Фрагмент кода, который изолируется таким способом, называется критической секцией (critical section), для его создания также применяется ключевое слово synchronized. На этот раз слово synchronized определяет объект, блокировка которого должна использоваться для синхронизации последующего фрагмента кода:
5упсИгоп12ес1(синхронизируемый0бъект) {