JavaRush /Java блог /Random UA /Рівень 26. Відповіді питання до співбесіди на тему рівня....
zor07
31 рівень
Санкт-Петербург

Рівень 26. Відповіді питання до співбесіди на тему рівня. Частина 2. Запитання 6-9, 11-12

Стаття з групи Random UA
Рівень 26. Відповіді питання до співбесіди на тему рівня.  Частина 2. Запитання 6-9, 11-12 - 1

6. Що таке канкаренсі?

Concurrency – це бібліотека класів Java, в якій зібрали спеціальні класи, оптимізовані для роботи з кількох ниток. Ці класи зібрані у пакеті java.util.concurrent. Їх можна схематично поділити за функціональною ознакою наступним чином: Рівень 26. Відповіді питання до співбесіди на тему рівня.  Частина 2. Запитання 6-9, 11-12 - 2Concurrent Collections — набір колекцій, що ефективно працюють у багатопотоковому середовищі, ніж стандартні універсальні колекції з java.utilпакета. Замість базового враппера Collections.synchronizedListз блокуванням доступу до всієї колекції використовуються блокування за сегментами даних або оптимізується робота для паралельного читання даних за wait-free алгоритмами. Queues- Неблокуючі та блокуючі черги з підтримкою багатопоточності. Неблокуючі черги заточені на швидкість та роботу без блокування потоків. Блокуючі черги використовуються, коли потрібно "пригальмувати" потоки "Producer" або "Consumer", якщо не виконані будь-які умови, наприклад, черга порожня або перепонена, або немає вільного "Consumer"'a. Synchronizers – допоміжні утиліти для синхронізації потоків. Є потужною зброєю в «паралельних» обчисленнях. Executors - містить у собі відмінні фрейморки для створення пулів потоків, планування роботи асинхронних завдань із отриманням результатів. Locks - являє собою альтернативні та більш гнучкі механізми синхронізації потоків у порівнянні з базовими synchronized,wait, notify, notifyAll. Atomics – класи з підтримкою атомарних операцій над примітивами та посиланнями. Джерело:

7. Які класи з "канкаренсі" ти знаєш?

Відповідь це питання добре викладено у цій статті . Змилу передруковувати всю її сюди я не бачу, тому наведу описи тільки тих класів, з якими мав честь побіжно ознайомитися. ConcurrentHashMap<K, V> — На відміну від Hashtableблоків і synhronizedна HashMap, дані представлені у вигляді сегментів, розбитих по hash'ам ключів. В результаті, для доступу до даних лине по сегментах, а не по одному об'єкту. На додаток, ітератори подають дані на певний час і не кидають ConcurrentModificationException. Що якщо в класі потрібно синхронізувати доступ до однієї простої змінної типу int? Можна використовувати конструкції зsynchronized, а при використанні атомарних операцій set/getпідійде також і volatile. Але можна зробити ще краще, використавши нові класи Atomic*. За рахунок використання CAS операції з цими класами працюють швидше, ніж якщо синхронізуватися через synchronized/volatile. Плюс існують методи атомарного додавання на задану величину, а також інкремент/декремент.

8. Як влаштований клас ConcurrentHashMap?

На момент появи ConcurrentHashMapJava-розробники потребували наступної реалізації хеш-карти:
  • Потокобезпека
  • Відсутність блокувань усієї таблиці на час доступу до неї
  • Бажано, щоб були відсутні блокування таблиці при виконанні операції читання
Основні ідеї реалізації ConcurrentHashMapтакі:
  1. Елементи картки

    На відміну від елементів HashMap, Entryоголошені ConcurrentHashMapяк volatile. Це важлива особливість, пов'язана також зі змінами в JMM .

    static final class HashEntry<K, V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K, V> next;
    
        HashEntry(K key, int hash, HashEntry<K, V> next, V value) {
            this .key = key;
            this .hash = hash;
            this .next = next;
            this .value = value;
         }
    
        @SuppressWarnings("unchecked")
        static final <K, V> HashEntry<K, V>[] newArray(int i) {
            return new HashEntry[i];
        }
    }
  2. Хеш-функція

    ConcurrentHashMapтакож використовується покращена функція хешування.

    Нагадаю, якою вона була з HashMapJDK 1.2:

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    Версія з ConcurrentHashMap JDK 1.5:

    private static int hash(int h) {
        h += (h << 15) ^ 0xffffcd7d;
        h ^= (h >>> 10);
        h += (h << 3);
        h ^= (h >>> 6);
        h += (h << 2) + (h << 14);
        return h ^ (h >>> 16);
    }

    У чому потреба ускладнення хеш-функції? Таблиці в хеш-карті мають довжину, що визначається ступенем двійки. Для хеш-кодів, двійкові уявлення яких не відрізняються в молодшій та старшій позиції, ми матимемо колізії. Ускладнення хеш-функції вирішує цю проблему, зменшуючи ймовірність колізій в карті.

  3. Сегменти

    Карта ділиться на N різних сегментів (16 за замовчуванням, максимальне значення може бути 16-бітним і є ступенем двійки). Кожен сегмент є потокобезпечною таблицею елементів карти. Збільшення кількості сегментів сприятиме тому, що операції модифікації стосуватимуться різних сегментів, що зменшить ймовірність блокувань під час виконання.

  4. ConcurrencyLevel

    Цей параметр впливає на використання картки пам'яті та кількість сегментів у картці.

    Кількість сегментів буде вибрано як найближчий ступінь двійки, більший за конкурентний рівень. Заниження конкуренціїРівень веде до того, що більш вірогідні блокування потоками сегментів картки під час запису. Завищення показника призводить до неефективного використання пам'яті. Якщо лише один потік змінюватиме карту, а решта читатиме — рекомендується використовувати значення 1.

  5. Разом

    Отже, основні переваги та особливості реалізації ConcurrentHashMap:

    • Карта має схожий на hashmapінтерфейс взаємодії
    • Операції читання не потребують блокувань та виконуються паралельно
    • Операції запису часто можуть виконуватися паралельно без блокувань
    • При створенні вказується необхідний concurrencyLevel, який визначається за статистикою читання та запису
    • Елементи картки мають значення value, оголошене якvolatile
    Джерело: Як працює ConcurrentHashMap

9. Що таке клас Lock?

Для керування доступом до спільного ресурсу як альтернатива оператору synchronized ми можемо використовувати блокування. Функціональність блокувань укладена в пакеті java.util.concurrent.locks. Спочатку потік намагається отримати доступ до спільного ресурсу. Якщо він вільний, то потік на нього накладає блокування. Після завершення роботи блокування із загального ресурсу знімається. Якщо ж ресурс не вільний і на нього вже накладено блокування, то потік очікує, поки це блокування не буде знято. Класи блокувань реалізують інтерфейс Lock, який визначає такі методи:
  • void lock():очікує, доки не буде отримано блокування
  • boolean tryLock():намагається отримати блокування, якщо блокування отримане, то повертає true . Якщо блокування не отримане, повертає false . На відміну від методу lock()не очікує отримання блокування, якщо воно недоступне
  • void unlock():знімає блокування
  • Condition newCondition():повертає об'єкт Condition, який пов'язаний з поточним блокуванням
Організація блокування у випадку досить проста: щоб одержати блокування викликається метод lock(), а по закінченні роботи із загальними ресурсами викликається метод unlock(), який знімає блокування. Об'єкт Conditionдозволяє керувати блокуванням. Як правило, для роботи з блокуваннями використовується клас ReentrantLockіз пакета java.util.concurrent.locks.Цей клас реалізує інтерфейс Lock. Розглянемо використання Java Lock API на прикладі невеликої програми: Отже, нехай у нас є клас Resourceз парочкою потокобезпечних методів та методів, де потокобезпека не потрібна.
public class Resource {

    public void doSomething(){
        // пусть здесь происходит работа с базой данных
    }

    public void doLogging(){
        // потокобезопасность для логгирования нам не требуется
    }
}
А тепер беремо клас, який реалізує інтерфейс Runnableта використовує методи класу Resource.
public class SynchronizedLockExample implements Runnable{

    // экземпляр класса Resource для работы с методами
    private Resource resource;

    public SynchronizedLockExample(Resource r){
        this.resource = r;
    }

    @Override
    public void run() {
        synchronized (resource) {
            resource.doSomething();
        }
        resource.doLogging();
    }
}
А тепер перепишемо наведену вище програму з використанням Lock API замість ключового слова synchronized.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{

    private Resource resource;
    private Lock lock;

    public ConcurrencyLockExample(Resource r){
        this.resource = r;
        this.lock = new ReentrantLock();
    }

    @Override
    public void run() {
        try {
            // лочим на 10 секунд
            if(lock.tryLock(10, TimeUnit.SECONDS)){
            resource.doSomething();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            //убираем лок
            lock.unlock();
        }
        // Для логгирования не требуется потокобезопасность
        resource.doLogging();
    }

}
Як видно з програми, ми використовуємо метод tryLock(), щоб переконатися, що потік чекає лише певний час. Якщо він не отримує блокування на об'єкт, то просто логує та виходить. Ще один важливий момент. Необхідно використовувати блок try-finally, щоб переконатися, що блокування буде знято, навіть якщо метод doSomething()кине виняток. Джерела:

11. Що таке mutex?

Мютекс – це особливий об'єкт для синхронізації ниток/процесів. Він може приймати два стани – зайнятий та вільний. Якщо спростити, то мютекс - це boolean-змінна, яка приймає два значення: зайнятий (true) і вільний (false). Коли нитка хоче монопольно володіти деяким об'єктом, вона позначає його зайнятим, а коли закінчила роботу з ним - позначає його вільним. Мютекс прикріплений до кожного об'єкта Java. Прямий доступ до мютекс є тільки у Java-машини. Від програміста він прихований.

12. Що таке монітор?

Монітор – це спеціальний механізм (шматок коду) – надбудова над мютексом, який забезпечує правильну роботу з ним. Адже мало помітити, що об'єкт – зайнятий, треба забезпечити, щоб інші нитки не пробували скористатися зайнятим об'єктом. У Java монітор реалізований за допомогою ключового слова synchronized. Коли ми пишемо блок synchronized, то компілятор Java замінює його трьома шматками коду:
  1. На початку блоку synchronizedдодається код, який зазначає мютекс як зайнятий.
  2. Наприкінці блоку synchronizedдодається код, який зазначає мютекс як вільний.
  3. Перед блоком synchronizedдодається код, який дивиться, якщо мютекс зайнятий – то нитка має чекати на його звільнення.
Частина 1
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ