Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
41
Добавлен:
27.03.2015
Размер:
244.74 Кб
Скачать

Класс java.lang.ThreadLocal (3)

 

Методы класса ThreadLocal<T>

T

get()

Возвращает значение текущей локальной копии этой

 

 

thread-local переменной.

protected T

initialValue()

Вызывается из метода get в том случае, если thread-local

 

 

переменная не инициализирована. Базовый метод

 

 

возвращает значение null. Замещающий метод должен

 

 

возвратить начальное значение текущей локальной

 

 

копии этой thread-local переменной. Если это связано с

 

 

изменением состояния каких-либо других объектов

 

 

приложения, то замещающий метод нужно

 

 

синхронизировать.

void

remove()

Удаляет текущее значение локальной копии этой thread-

 

 

local переменной.

void

set(T value)

Устанавливает новое значение.

Базовые средства Java для синхронизации потоков (1)

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

обработку (типичная задача "писатель - читатель"). Вот простое, но неверное решение: public class DataManager {

private static boolean ready = false; … // поток обработки

public void processingData( ) {

while ( !ready ) { // цикл ожидания, занимающий процессор System.out.println("Waiting for data...");

}

ready = false; System.out.println("Processing data...");

}

… // поток подготовки public void prepareData() {

System.out.println("Data prepared"); ready = true;

}

}

Базовые средства Java для синхронизации потоков (2)

Ожидание с использованием блокировки synchronized и методов wait() и notifyAll() класса

java.lang.Object (почти правильное решение): public class DataManager {

private static final Object monitor = new Object( ); … // поток обработки

public void processingData( ) { synchronized ( monitor ) {

System.out.println( "Waiting for data..." ); try {

monitor.wait(); // не безопасно: поток может "проснуться" без причины } catch (InterruptedException e) { e.printStackTrace(); }

} } System.out.println("Processing data..."); … // поток подготовки

public void prepareData( ) { synchronized ( monitor ) {

}

System.out.println( "Data prepared" ); } } monitor.notifyAll( );

Вызовы методов wait(), notify() и notifyAll() должны обязательно находиться внутри блока synchronized, либо внутри synchronized-метода, иначе в runtime будет выброшено Exception. Как только поток достигает метода wait(), блокировка по synchronized снимается, а поток уходит в сон.

Базовые средства Java для

синхронизации потоков (3)

private static boolean ready = false;

private static final Object monitor = new Object( ); … // поток обработки

public void processingData( ) { synchronized ( monitor ) {

System.out.println( "Waiting for data..." ); while ( !ready ) {

try {monitor.wait();

} catch (InterruptedException e) { e.printStackTrace(); }

}

ready = false; System.out.println("Processing data...");

}

}// поток подготовки

public void prepareData( ) { synchronized ( monitor ) {

System.out.println( "Data prepared" ); ready = true;

}monitor.notifyAll( );

}

Базовые средства Java для синхронизации потоков (4)

Категорически рекомендуется всегда вызывать метод wait() внутри цикла, проверяющего флаг готовности данных, доступ к которым синхронизируется. Это гарантирует:

1. Живучесть системы потоков. Если по каким-либо причинам метод notify() (или notifyAll()) будет вызван другим потоком раньше, чем ожидающим был вызван метод wait(),

то это поток может никогда не проснуться, несмотря на то, что данные уже готовы к обработке.

2. Безопасность системы потоков. В некоторых виртуальных машинах Java реализуется такая стратегия управления потоками, при которой не исключены ложные пробуждения (выход из состояния wait без notify). Наличие цикла проверки флага готовности, обрамляющего вызов метода wait() и предшествующего операторам обработки данных гарантирует, что в случае ложного пробуждения поток не займется обработкой несуществующих или некорректных данных.

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

При использовании любых средств синхронизации всегда нужно тщательно следить за тем, чтобы не попасть в ситуацию deadlock (смертельное объятие), возникающую так:

поток A захватывает ресурс X и ждет освобождения ресурса Y

поток B захватывает ресурс Y и ждет освобождения ресурса X

Базовые средства Java для синхронизации потоков (5)

Синхронизация, основанная на базовом synchronized, хотя и работает, но имеет очень существенные недостатки:

Не существует способа отказаться от попытки захватить какой-либо объект, если он занят. Отсутствует возможность отказаться от попытки захвата объекта через какой-то интервал времени. Если бы эти возможности были, то проблема появления deadlock при синхронизации потоков была бы не так страшна.

Не существует способа осуществлять отдельные блокировки для чтения или записи, что иногда было бы весьма полезно. При освобождении некоторого захваченного ресурса (того, который выступает параметром у вызова synchronized блока) нет возможности дать доступ к этому блоку самому первому потоку, который раньше других начал попытался этот ресурс захватить.

Если существует несколько вложенных synchronized блоков, то освобождены ресурсы должны быть строго в обратном порядке по сравнению с тем, в котором они были захвачены. Иногда бывает не просто обеспечить именно такой порядок освобождения.

Есть типичные ошибки и при запуске потоков. Если вместо метода start( ) прямо вызвать перегруженный метод run( ), то программа без всяких сообщений или исключений отработает, но не в многопоточном, а в однопоточном режиме.

Еще одна неприятность заключается в том, что если написать метод run() с другой сигнатурой (например public int run( )), то, опять-таки без сообщений и

исключений, при вызове метода start( ) будет запущен на исполнение неперегруженный метод run( ) базового класса Thread, который просто вообще ничего не делает.

Соседние файлы в папке Презентации по Java