Java - Повторный монитор

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

Java - Reentrant Monitor

Что такое Повторный монитор?

Прежде чем мы углубимся в детали, давайте поймем, что такое Повторный монитор. Представьте, что вы находитесь в магической библиотеке, где только один человек может войти в определенный раздел за один раз. А что, если вы уже находитесь в этом разделе и хотите зайти глубже в подраздел? Повторный монитор - это как магический пропуск, который позволяет вам сделать это - войти в раздел, в котором вы уже находитесь!

На языке Java Повторный монитор позволяет потоку, уже удерживающему замок, снова его получить, не блокируясь. Это как дать себе разрешение войти в комнату, в которой вы уже находитесь. Круто, правда?

Why Do We Need Reentrant Monitors? (Why do we need this magical pass?)

Вы можете задаться вопросом: "Зачем нам этот магический пропуск?" В мире многопоточности (где одновременно работают несколько частей программы), мы часто должны защищать общие ресурсы. Повторные мониторы помогают нам сделать это более эффективно, особенно когда у нас есть методы, вызывающие другие методы, которые также нуждаются в том же замке.

Введение в ReentrantLock

Java предоставляет нам класс под названием ReentrantLock для реализации Повторных мониторов. Это как наш магический библиотечный пропуск, но в виде кода!

Синтаксис

Вот как мы создаем и используем ReentrantLock:

import java.util.concurrent.locks.ReentrantLock;

ReentrantLock lock = new ReentrantLock();

// Закрытие замка
lock.lock();
try {
// Ваща защищенная кода здесь
} finally {
// Открывание замка
lock.unlock();
}

Не волнуйтесь, если это выглядит немного пугающе. Мы разберем это на примерах!

Многопоточность без Reentrant Lock (Multithreading Without Reentrant Lock)

Давайте начнем с простого примера без использования ReentrantLock. Представьте, что у нас есть магический счетчик, который пытаются увеличить несколько магов (потоков):

public class MagicalCounter {
private int count = 0;

public void increment() {
count++;
}

public int getCount() {
return count;
}
}

Теперь создадим несколько потоков магов для увеличения этого счетчика:

public class WizardThread extends Thread {
private MagicalCounter counter;

public WizardThread(MagicalCounter counter) {
this.counter = counter;
}

public void run() {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
}
}

public class MagicalCounterTest {
public static void main(String[] args) throws InterruptedException {
MagicalCounter counter = new MagicalCounter();
WizardThread wizard1 = new WizardThread(counter);
WizardThread wizard2 = new WizardThread(counter);

wizard1.start();
wizard2.start();

wizard1.join();
wizard2.join();

System.out.println("Конечное значение: " + counter.getCount());
}
}

Если вы запустите это, вы можете ожидать, что конечное значение будет 2000 (1000 увеличений от каждого мага). Но сюрприз! Результат часто меньше 2000. Это потому, что наши маги наступают друг другу на ноги - они пытаются увеличить счетчик одновременно, что приводит к потерянным увеличениям.

Многопоточность с Reentrant Lock (Multithreading With Reentrant Lock)

Теперь давайте добавим немного магии ReentrantLock к нашему счетчику:

import java.util.concurrent.locks.ReentrantLock;

public class MagicalCounterWithLock {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}

public int getCount() {
return count;
}
}

Давайте разберем это:

  1. Мы создаем объект ReentrantLock под названием lock.
  2. В методе increment мы вызываем lock.lock() перед увеличением счетчика.
  3. Мы используем блок try-finally, чтобы всегда открывать замок, даже если occurs例外.
  4. После увеличения мы вызываем lock.unlock() в блоке finally.

Теперь, если мы запустим наш тест WizardThread с этим новым MagicalCounterWithLock, мы всегда получим 2000 как конечное значение. Наши маги теперь nicely礼ят по очереди!

Многопоточность с Reentrant Lock как True (Multithreading With Reentrant Lock as True)

ReentrantLock имеет еще один трюк. Мы можем создать его с параметром справедливости:

ReentrantLock fairLock = new ReentrantLock(true);

Когда мы устанавливаем справедливость в true, замок favorит предоставление доступа потоку, который ждет дольше всего. Это как formation formation для наших магов!

Вот как мы можем использовать это:

public class FairMagicalCounter {
private int count = 0;
private ReentrantLock fairLock = new ReentrantLock(true);

public void increment() {
fairLock.lock();
try {
count++;
} finally {
fairLock.unlock();
}
}

public int getCount() {
return count;
}
}

Это обеспечивает, что если несколько магов ждут, чтобы увеличить счетчик, тот, кто ждет дольше всех, получит доступ следующим.

Заключение (Conclusion)

И вот оно, молодые маги! Мы отправились в магическое путешествие по миру Повторных мониторов в Java. Мы увидели, как они помогают нам управлять общими ресурсами в многопоточных средах, обеспечивая правильное увеличение наших магических счетчиков (и других общих объектов).

Помните, как и любая сильная магия, Повторные мониторы должны использоваться wisely. Они великолепны для управления одновременным доступом к общим ресурсам, но чрезмерное использование может привести к снижению производительности или даже к deadlocks (ситуация, когда маги застревают в ожидании друг друга's замков навсегда!).

Практикуйте эти заклинания... err, примеры кода, и вскоре вы будете casts casting multithreading magic like a pro! Happy coding, and may your threads always be in harmony!

Credits: Image by storyset