Канкаренси, BlockingQueues (java7) - 1

— Привет, Амиго!

— Привет, Ким!

— Я тебе сегодня расскажу про «канкаренси» — Concurrency.

Concurrency – это библиотека классов в Java, в которой собрали специальные классы, оптимизированные для работы из нескольких нитей. Эта тема очень интересная и обширная. Но сегодня мы просто познакомимся с ней. Эти классы собраны в пакете java.util.concurrent. Я расскажу про пару интересных классов.

Атомарные типы.

Ты уже знаешь, что даже операция count++ не является потокобезопасной (thread-safe). При увеличении переменной на 1 реально происходит три операции, в результате чего может возникнуть конфликт при одновременном изменении этой переменной.

— Ага, Элли рассказывала это немного раньше:

Нить 1 Нить 2 Результат
register1 = count;
register1++;
count = register1;
register2 = count;
register2++;
count = register2;
register1 = count;
register2 = count;
register2++;
count = register2;
register1++;
count = register1;

— Именно. Тогда в Java были добавлены типы данных, которые выполняют такие операции неразрывно – атомарно. (Атом — неделимый).

Так в Java появились типы AtomicInteger, AtomicBoolean, AtomicDouble и т.д.

Вот, допустим нам нужно сделать класс «счетчик»:

Пример
class Counter
{
 private int c = 0;

 public void increment()
 {
  c++;
 }

 public void decrement()
 {
  c--;
 }

 public int value()
 {
  return c;
 }
}

Как бы ты сделал его объекты thread-safe?

— Да, сделал бы все методы synchronized и все:

Пример
class synchronized Counter
{
 private int c = 0;

 public synchronized void increment()
 {
  c++;
 }

 public synchronized void decrement()
 {
  c--;
 }

 public synchronized int value()
 {
  return c;
 }
}

— Отличная работа. А вот, как бы он выглядел с использованием атомарных типов:

Пример
class AtomicCounter
{
 private AtomicInteger c = new AtomicInteger(0);

 public void increment()
 {
  c.incrementAndGet();
 }

 public void decrement()
 {
  c.decrementAndGet();
 }

 public int value()
 {
  return c.get();
 }
}

И твой и мой классы работают одинаково, но класс с AtomicInteger работает быстрее.

— Т.е. разница небольшая?

Да. Из своего опыта я могу посоветовать всегда в лоб использовать synchronized. И только когда весь код приложения уже написан и начинается процесс его оптимизации, то можно начинать переписывать его части с использованием Atomic-типов. Но в любом случае, я бы хотела, чтобы ты знал, что такие типы есть. Даже если не будешь активно ими пользоваться, ты всегда можешь увидеть код, написанный с их применением.

— Согласен, в это есть смысл.

— Кстати, а ты заметил, что атомарные типы – не immutable? AtomicInteger, в отличии от просто Integer, содержит методы по изменению своего внутреннего состояния.

— Понятно, прямо как String и StringBuffer.

— Да, что-то типа того.

Нитебезопасные (иногда называют — потокобезопасные) коллекции.

В качестве такой коллекции я хотела бы привести ConcurrentHashMap. Как сделать HashMap thread-safe?

— Сделать все его методы synchronized?

— Да, но представь теперь, что у тебя есть один такой SynchronizedHashMap, а к нему обращаются десятки нитей. И сто раз в секунду в этот map добавляется новая запись, при этом весь объект блокируется для чтения и записи.

— Да, но это же стандартный подход. Что тут можно сделать?

— Разработчики Java придумали несколько крутых штук.

Во-первых, они хранят данные в ConcurrentHashMap не одним куском, а разбивая их на порции — «корзины». И когда кто-то меняет данные в ConcurrentHashMap, то блокируется не весь объект, а только одна корзина, к которой происходит доступ. Т.е. на самом деле объект могут одновременно менять много нитей.

Во-вторых, помнишь проблему, что нельзя одновременно идти по элементам списка/мэпа и менять его? Такой код кинет исключение:

Нельзя одновременно идти в цикле по элементам коллекции и менять ее
HashMap<String, Integer> map = new HashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key)==0)
  map.remove(key);
}

А в ConcurrentHashMap это можно:

Нельзя одновременно идти в цикле по элементам коллекции и менять ее
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();

for (String key: map.keySet())
{
 if (map.get(key)==0)
  map.remove(key);
}

У Concurrency есть очень много преимуществ. Просто надо очень хорошо разобраться в этих классах, чтобы их применять.

— Ясно. Спасибо, Ким. Действительно, очень интересные классы. Надеюсь, когда-нибудь я тоже буду виртуозно ими владеть.