Канкаренсі, 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 є дуже багато переваг. Просто треба добре розібратися в цих класах, щоб їх застосовувати.

— Зрозуміло. Дякую, Кім. Справді, дуже цікаві класи. Сподіваюся, колись я теж віртуозно ними володітиму.