Чтение онлайн

на главную - закладки

Жанры

Философия Java3

Эккель Брюс

Шрифт:

//: concurrency/AtomicityTest.java

import java.util.concurrent.*;

public class AtomicityTest implements Runnable { private int i = 0; public int getValueO { return i; } private synchronized void evenIncrement О { i++, i++. } public void run { while(true)

evenlncrementO;

}

public static void mam(String[] args) {

ExecutorService exec = Executors newCachedThreadPoolО; AtomicityTest at = new AtomicityTest; exec execute(at). while(true) {

int val = at.getValueO; if(val % 2 != 0) {

System.out.println(val), System.exit(0);

} /* Output

191583767

*///:-

Однако

программа находит нечетные значения и завершается. Хотя return i и является атомарной операцией, отсутствие synchronized позволит читать значение объекта, когда он находится в нестабильном промежуточном состоянии. Вдобавок переменная i не объявлена как volatile, а это приведет к проблемам с видимостью. Оба метода, getValue и evenlncrement, должны быть объявлены синхронизируемыми. Только эксперты в области параллельных вычислений могут пытаться применять оптимизацию в подобных случаях.

В качестве второго примера рассмотрим кое-что еще более простое: класс, производящий серийные номера25. Каждый раз при вызове метода nextSerialNum-ber он должен возвращать уникальное значение:

//: concurrency/SerialNumberGeneratorJava

public class SerialNumberGenerator {

private static volatile int serial Number = 0, public static int nextSerialNumberО {

return serialNumber++; // Операция не является потоково-безопасной

}

} ///.-

Представить себе класс тривиальнее SerialNumberGenerator вряд ли можно, и если вы ранее работали с языком С++ или имеете другие низкоуровневые навыки, то, видимо, ожидаете, что операция инкремента будет атомарной, так как инкремент обычно реализуется в виде одной инструкции микропроцессора. Однако в виртуальной машине Java инкремент не является атомарным и состоит из чтения и записи, соответственно, ниша для проблем с потоками найдется даже в такой простой программе.

Поле serialNumber объявлено как volatile потому, что каждый поток обладает локальным стеком и поддерживает в нем копии некоторых локальных переменных. Если вы объявляете переменную как volatile, то тем самым указываете компилятору не проводить оптимизацию, а это приведет к удалению чтения и записи, удерживающих поле в соответствии с локальными данными потока. Операции чтения и записи осуществляются непосредственно с памятью без кэширования. Кроме того, volatile не позволяет компилятору изменять порядок обращений с целью оптимизации. И все же присутствие volatile не влияет на тот факт, что инкремент не является атомарной операцией.

Для тестирования нам понадобится множество, которое не потребует переизбытка памяти в том случае, если обнаружение проблемы отнимет много времени. Приведенный далее класс 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 и т. д. Эти классы содержат атомарную операцию условного обновления в форме

Поделиться:
Популярные книги

Архивариус

alchoz
Проект «Поттер-Фанфикшн»
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Архивариус

Андер Арес

Грехов Тимофей
1. Андер Арес
Фантастика:
рпг
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Андер Арес

Звезды над Самаркандом

Бородин Сергей Петрович
Проза:
историческая проза
7.50
рейтинг книги
Звезды над Самаркандом

Домашняя медицинская энциклопедия. Симптомы и лечение самых распространенных заболеваний

Коллектив авторов
Научно-образовательная:
медицина
5.00
рейтинг книги
Домашняя медицинская энциклопедия. Симптомы и лечение самых распространенных заболеваний

Двадцать один год

Кинешемцева Мелания
Любовные романы:
прочие любовные романы
5.00
рейтинг книги
Двадцать один год

Фантастика. Путеводитель

Weeldoon
Справочная литература:
руководства
5.00
рейтинг книги
Фантастика. Путеводитель

10 вождей. От Ленина до Путина

Млечин Леонид Михайлович
Документальная литература:
биографии и мемуары
5.20
рейтинг книги
10 вождей. От Ленина до Путина

Неучтенный элемент. Том 8

NikL
8. Антимаг. Вне системы
Фантастика:
фэнтези
5.00
рейтинг книги
Неучтенный элемент. Том 8

Судьба

Буджолд Лоис Макмастер
Мастера фантазии
Фантастика:
научная фантастика
космическая фантастика
5.00
рейтинг книги
Судьба

Я бог. Книга XXXIX

Дрейк Сириус
39. Дорогой барон!
Фантастика:
юмористическое фэнтези
аниме
попаданцы
5.00
рейтинг книги
Я бог. Книга XXXIX

Ерагон. Брізінгр, або Сім обіцянок Ерагона й Сапфіри

Паолини Кристофер
3. Спадок
Фантастика:
фэнтези
7.50
рейтинг книги
Ерагон. Брізінгр, або Сім обіцянок Ерагона й Сапфіри

ФЭНТЕЗИ-2004

Пехов Алексей Юрьевич
Фантастика:
фэнтези
5.75
рейтинг книги
ФЭНТЕЗИ-2004

Экспансия (сборник)

Басов Николай Владленович
Шедевры отечественной фантастики
Фантастика:
научная фантастика
5.00
рейтинг книги
Экспансия (сборник)

Пепел на обелиске

Трофимов Ерофей
5. Дракон
Фантастика:
боевая фантастика
космическая фантастика
6.91
рейтинг книги
Пепел на обелиске