Слайд 1Лекции по Java SE
Александр Харичкин, Performance Support Manager, NetCracker
Лекция 9.
Многопоточность
в Java.
Синхронизация. Пакеты библиотек java.util.concurrent
Слайд 2План лекции
Многозадачность (multithreading) и Java API
Поддержка многозадачности в Java
Способы создания
потока
Управление потоком
Потоки-демоны
Приоритеты потоков
Одновременный доступ (concurrency) и синхронизация
Java Concurrency API
Слайд 3Многозадачность (многопоточность)
Поток – часть приложения, исполняющая определенную задачу
Многозадачность – возможность
исполнять несколько независимых задач одновременно
Потоки разделяют все системные ресурсы, при
этом память разделяют только взаимодействующие потоки
ОС управляет исполнением потоков и их доступом к общим ресурсам
Что такое многозадачность?
Серверные приложения (параллельная обработка нескольких запросов)
Графические приложения
Обработка большого объема данных
Другие фоновые операции
Зачем нужна многозадачность?
Слайд 4Многозадачность в Java
Java поддерживает многозадачность и предоставляет интерфейс управления потоками
(каждый поток представляется экземпляром класса java.lang.Thread)
Java предоставляет интерфейс взаимодействия между
потоками
Java поддерживает синхронизацию доступа к данным
К примеру, Garbage Collector работает в отдельном потоке.
Слайд 5Создание потока исполнения – способ №1
Способ №1: наследование от класса
java.lang.Thread
//Объявление
public class MyThread extends Thread {
public void run (){//Переопределение метода
//Действия,
выполняемые потоком
}
}
//Запуск
public class ThreadTest1 {
public static void main (String args[]){
Thread thread = new MyThread ();
thread.start ();// Здесь будет вызван метод run()
}
}
Достоинство – минимум кода. Недостатки:
Нельзя наследоваться от другого класса
Неверный смысл: идеологическая связь с наследованием
Слайд 6Создание потока исполнения – способ №2
Способ №2: реализация интерфейса java.lang.Runnable
//Объявление
public
class MyRunnable implements Runnable {
public void run (){//Реализация метода
//Действия, выполняемые
потоком
}
}
//Запуск
public class ThreadTest2 {
public static void main (String args[]){
Runnable run = new MyRunnable ();
Thread thread = new Thread (run);
thread.start ();// Здесь будет вызван метод run()
}
}
Слайд 7Управление исполнением потока
public static void sleep(long millis) throws InterruptedException
приостанавливает текущий
поток как минимум на millis миллисекунд
public static void yield()
приостанавливает текущий
поток, предоставляя возможность выполнять другие потоки (обычно необходимо в циклах, если там не вызывается sleep)
public final void join() throws InterruptedException
вводит текущий поток в ожидание завершения другого потока
public final boolean isAlive()
получает состояние потока (true – если поток запущен и выполняется,
false – если поток не был запущен или завершился)
public final void stop()
насильственно останавливает поток, неконтролируемо освобождая все ресурсы
public final void suspend()
приостанавливает выполнение до вызова метода resume();
зачастую приводит к deadlock
public final void resume()
возобновляет выполнение потока, приостановленного методом suspend()
deprecated
Слайд 8Управление исполнением потока: прерывание
Правильное прерывание потоков:
public void interrupt()
пытается выставить статус
потока в “прерванный”, иначе (если поток находится в ожидании) очищает
статус и порождает InterruptedException в потоке
public boolean isInterrupted() - проверяет статус потока
public static boolean interrupted()
проверяет статус текущего потока, при этом очищая его
Используя interrupt(), нельзя остановить поток без его “согласия”
Чтобы поток можно было корректно остановить, вместо
while (condition){..}
while (condition && !Thread.currentThread().isInterrupted()) {..}
нужно писать
+ при вызове методов sleep, join нужно в catch(InterruptedException) завершать run()
Слайд 9Потоки-демоны
Демон – это поток, который может существовать неограниченное время.
Если в
программе остаются только потоки-демоны, то такая программа завершает работу. В
противном случае она ожидает завершения всех потоков, не являющихся демонами.
Назначение демонов – обслуживание других потоков
Например, таймер
API
задание типа потока (true – демон, false – иначе):
public final void setDaemon(boolean on)
получение типа потока:
public final boolean isDaemon()
Слайд 10Приоритеты потоков
Задание приоритета потока
public final void setPriority(int newPriority);
Приоритет изменяется
от Thread.MIN_PRIORITY (1) до Thread.MAX_PRIORITY (10) включительно.
Нормальный приоритет (default): Thread.NORM_PRIORITY
(5).
Получение приоритета потока
public final int getPriority();
Количество уровней приоритетов зависит от конкретной платформы
Не рекомендуется слишком полагаться на приоритеты
(эффективность зависит от ОС, если она поддерживает приоритеты)
Выставление высоких приоритетов у нескольких потоков может привести с тому, что поток с низким приоритетом не получит процессорного времени
Слайд 11План лекции
Многозадачность (multithreading) и Java API
Одновременный доступ (concurrency) и синхронизация
Проблемы
одновременного доступа
Синхронизация и ее недостатки
Операции, не требующие синхронизации
Блокирующие операции
Монитор
Java Concurrency
API
Слайд 12Проблемы одновременного доступа
При неконтролируемом доступе разными потоками данные могут быть
повреждены.
Race condition – ошибка проектирования многозадачной системы, при которой работа
системы зависит от того, в каком порядке выполняются части кода. Состояние гонки — специфический баг, проявляющийся в случайные моменты времени и «затихающий» при попытке его локализовать.
В многопроцессорных системах необходима подгрузка разделяемых значений в память потока и запись в общую память.
Критическая секция – часть программы, в которой осуществляется доступ к разделяемым данным. Должна одновременно исполняться не более, чем одним потоком.
Слайд 13Синхронизация
Синхронизированный блок
synchronized (someObj /*объект синхронизации*/){
//при входе в блок блокируется объект
someObj
//тело блока
}
public synchronized void someMethod (){
/*при входе в метод блокируется
объект (запрет вызова любого синхронизированного метода объекта)*/
//тело метода
}
public void someMethod (){
synchronized(this){
//тело метода
}
}
Cтатические методы синхронизуются по объекту-классу (SomeClass.class)
Синхронизированный метод
Эквивалентный код:
Слайд 14Недостатки синхронизации
Невозможно прервать поток, пытающийся получить блокировку.
Невозможно задать тайм-аут для
операции получения блокировки.
В некоторых случаях единственное условие на операцию блокировки
неэффективно.
Примитивы JVM не всегда эффективно отображаются в возможности ОС.
Механизм синхронизации весьма дорогостоящий (понижает скорость).
Дополнительный внутренний учетный код.
Синхронизацию рекомендуется использовать лишь там, где это нужно
Издержки синхронизации
Слайд 15Операции, не требующие синхронизации
Атомарные операции. Все операции над числами примитивного
типа.
Операции с полями типа final.
Операции с полями типа volatile всегда
атомарные
При чтение значения volatile-переменной оно всегда читается из общей памяти.
При записи значения volatile-переменной оно всегда пишется в общую память.
Обращение к volatile переменным занимает больше времени (однако это не так расточительно как дополнительный блок synchronized).
Операции над данными, доступными по volatile ссылке,
могут быть не атомарны!
Слайд 16Блокирующие операции
Блокирующие вызовы (IO, network, …) внутри synchronized методов.
При вызове
sleep() ресурсы, монопольно используемые текущим потоком, остаются недоступными для других.
Взаимные
блокировки (deadlocks) – остановка потоков из-за взаимозависимости между общими синхронизированными ресурсами; выявляются трудно.
// Class A
synchronized public void methodA (B b) {
b.methodB(this);
}
// Class B
synchronized public void methodB (A a) {
a.methodA(this);
}
Слайд 17Монитор
Монитор (по определению) – объект класса, содержащего только закрытые поля.
Но у мониторов Java поля необязательно закрытые.
Любой Java-объект может быть
монитором.
Для взаимодействия с монитором поток должен иметь блокировку на него (синхронизироваться по нему).
Методы монитора (класс Object):
wait(), wait(time) – ожидание монитора.
notify() – извещение одного из ждущих потоков (произвольного).
notifyAll() – извещение всех ждущих потоков
Для работы с монитором поток должен им владеть (т.е. исполнять код, синхронизованный по данному объекту)
Слайд 18План лекции
Многозадачность (multithreading) и Java API
Одновременный доступ (concurrency) и синхронизация
Java
Concurrency API
Слайд 19Java Concurrency API
В Java 5 добавлены новые инструменты синхронизации
(пакет
java.util.concurrent), в том числе:
Набор классов для атомарных операций с объектами
(java.util.concurrent.atomic)
Новые инструменты работы с блокировками (java.util.concurrent.locks)
Синхронизованные аналоги некоторых классов из пакета java.util
Слайд 20java.util.concurrent.atomic
Набор классов с атомарными методами
AtomicReference
AtomicInteger
…
Основной метод всех этих классов:
boolean
compareAndSet (V expected, V update)
Атомарно проставляет новое значение, если текущее
значение равно ожидаемому. Возвращает результат выполнения операции (успех/неудача).
Метод реализуется через соответствующую инструкцию процессора, поэтому его грамотное использование позволяет повысить производительность по сравнению с блокировками.
Кроме общих методов, некоторые классы предоставляют атомарные реализации специфических методов.
Слайд 21java.util.concurrent.atomic – пример
На основе классов пакета можно разрабатывать неблокирующие алгоритмы.
К
примеру, реализация метода AtomicInteger.incrementAndGet:
public final int incrementAndGet ()
{
for (;;)
{
int current
= get ();
int next = current + 1;
if (compareAndSet (current, next)) return next;
}
}
Слайд 22java.util.concurrent.locks.Lock
Расширенный функционал по сравнению с использованием synchronized. Простейший пример использования:
Lock
l = ...;//Создание объекта блокировки
l.lock ();//Захват блокировки
try { //Код, защищенный
блокировкой
} finally {
l.unlock ();//Снятие блокировки
}
Более сложный пример:
Lock lock = ...;//Создание объекта блокировки
if (lock.tryLock ()) {//Попытка захватить блокировку
try {
//Код, защищенный блокировкой
} finally {
lock.unlock ();//Снятие блокировки
}
} else {
//Альтернативные действия
}
Слайд 23java.util.concurrent.locks.Condition
Аналог монитора. С каждым Condition объектом связан Lock, необходимый для
работы с Condition-ом.
/**
* Очередь элементов ограниченной емкости
* с
атомарными операциями
*/
class BoundedQueue
{
//Блокировка для синхронизации доступа к данным
final Lock lock = new ReentrantLock ();
//Сигнализирует о наличии свободного места
final Condition notFull = lock.newCondition ();
//Сигнализирует о наличии элементов
final Condition notEmpty = lock.newCondition ();
//Данные, доступ к которым надо синхронизовать
final Object[] items = new Object[100];
int putptr, takeptr, count;
Слайд 24java.util.concurrent.locks.Condition
/**
* Добавляет элемент в конец очереди
*/
public void put
(Object x) throws InterruptedException
{
lock.lock ();//Получение блокировки
try {
//Ожидание появления свободного места
/*
При
входе в метод await блокировка атомарно снимается и поток усыпляется. После пробуждения перед выходом из метода блокировка восстанавливается.
Цикл по условию необходимости ожидания нужен из-за паразитных пробуждений.
*/
while (count == items.length) notFull.await ();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal ();//Очередь теперь не пуста
} finally
{
lock.unlock ();//Снятие блокировки
}
}
Слайд 25java.util.concurrent.locks.Condition
/**
* Вынимает из очереди первый элемент
*/
public Object take
() throws InterruptedException
{
lock.lock ();//Получение блокировки
try {
//Ожидание появления нового элемента
while (count
== 0) notEmpty.await ();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal ();//В очереди есть место
return x;
} finally
{
lock.unlock();//Снятие блокировки
}
}
}
Слайд 26Что еще почитать
Хорстманн К.С., Корнелл Г. – Java, том 2,
глава 1
API References for java.util.concurrent: http://download.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html
Введение в неблокирующие алгоритмы:
http://www.ibm.com/developerworks/ru/library/j-jtp04186/