Condition
предоставляется в JDK 1.5 для заменыwait
а такжеnotify
Метод связи нить, то кто-то должен спросить:почему бы не использоватьwait
а такжеnotify
уже?Чувак, я хорошо им пользуюсь. Не волнуйся, приятель, послушай, я расскажу тебе подробно...
Почему рекомендуется использоватьCondition
вместоObject
серединаwait
а такжеnotify
Есть две причины:
- использовать
notify
В экстремальных условиях это заставит нить «симулировать смерть»; Condition
Более высокая производительность.
Далее, как использовать код и блок-схему для демонстрации двух вышеуказанных ситуаций.
1.Сообщить ветку "притворная смерть"
Так называемая нить "притворная смерть" означает, что при использованииnotify
Когда пробуждаются несколько ожидающих потоков, случайно пробуждается не готовый к работе поток, в результате чего вся программа переходит в заблокированное состояние и не может продолжать выполнение.
Взяв в качестве примера классическую модель производителя и потребителя в многопоточном программировании, давайте сначала продемонстрируем проблему «подвешенной смерти» потоков.
1.1 Обычная версия
Перед демонстрацией проблемы потока "симулировать смерть" мы сначала используемwait
а такжеnotify
Чтобы реализовать простую модель производителя и потребителя, чтобы сделать код более интуитивным, я напишу здесь очень простую версию реализации. Сначала создадим фабричный класс. Фабричный класс содержит два метода: один — метод (сохранения) для циклического создания данных, а другой — метод (извлечения) для циклического потребления данных. Код реализации выглядит следующим образом.
/**
* 工厂类,消费者和生产者通过调用工厂类实现生产/消费
*/
class Factory {
private int[] items = new int[1]; // 数据存储容器(为了演示方便,设置容量最多存储 1 个元素)
private int size = 0; // 实际存储大小
/**
* 生产方法
*/
public synchronized void put() throws InterruptedException {
// 循环生产数据
do {
while (size == items.length) { // 注意不能是 if 判断
// 存储的容量已经满了,阻塞等待消费者消费之后唤醒
System.out.println(Thread.currentThread().getName() + " 进入阻塞");
this.wait();
System.out.println(Thread.currentThread().getName() + " 被唤醒");
}
System.out.println(Thread.currentThread().getName() + " 开始工作");
items[0] = 1; // 为了方便演示,设置固定值
size++;
System.out.println(Thread.currentThread().getName() + " 完成工作");
// 当生产队列有数据之后通知唤醒消费者
this.notify();
} while (true);
}
/**
* 消费方法
*/
public synchronized void take() throws InterruptedException {
// 循环消费数据
do {
while (size == 0) {
// 生产者没有数据,阻塞等待
System.out.println(Thread.currentThread().getName() + " 进入阻塞(消费者)");
this.wait();
System.out.println(Thread.currentThread().getName() + " 被唤醒(消费者)");
}
System.out.println("消费者工作~");
size--;
// 唤醒生产者可以添加生产了
this.notify();
} while (true);
}
}
Далее создадим два потока, один из которых является вызовом производителя.put
метод, другой - вызов потребителяtake
метод, код реализации выглядит следующим образом:
public class NotifyDemo {
public static void main(String[] args) {
// 创建工厂类
Factory factory = new Factory();
// 生产者
Thread producer = new Thread(() -> {
try {
factory.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者");
producer.start();
// 消费者
Thread consumer = new Thread(() -> {
try {
factory.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者");
consumer.start();
}
}
Результат выполнения следующий:Из приведенных выше результатов видно, что производители и потребители попеременно выполняют задачи в цикле, и сцена очень гармонична, что является правильным результатом, который нам нужен.
1.2 Версия темы "притворная смерть"
Когда есть только один производитель и один потребитель,wait
а такжеnotify
Проблем с методом не будет, но при увеличении количества производителей до двух возникнет проблема "подвешенной смерти" потока Код реализации программы следующий:
public class NotifyDemo {
public static void main(String[] args) {
// 创建工厂方法(工厂类的代码不变,这里不再复述)
Factory factory = new Factory();
// 生产者
Thread producer = new Thread(() -> {
try {
factory.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者");
producer.start();
// 生产者 2
Thread producer2 = new Thread(() -> {
try {
factory.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者2");
producer2.start();
// 消费者
Thread consumer = new Thread(() -> {
try {
factory.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者");
consumer.start();
}
}
Результат выполнения программы следующий:Из приведенных выше результатов видно, что когда мы увеличим количество производителей до 2, это вызовет проблему блокировки выполнения потока «притворной смертью».Когда производитель 2 просыпается и снова блокируется, вся программа не может продолжать выполняться. . . .
Анализ темы "притворная смерть" темы
Сначала мы отмечаем шаги выполнения вышеуказанной программы и получаем следующие результаты:Как видно из приведенного выше рисунка:Когда выполняется шаг 4, производитель находится в рабочем состоянии в это время, в то время как производитель 2 и потребитель находятся в состоянии ожидания.Правильный способ в это время должен состоять в том, чтобы разбудить потребителя для потребления, а затем разбудить производитель после того, как потребитель закончил потреблять. Продолжайте работать, но в это время производитель по ошибке будит производителя 2, и производитель 2 не имеет возможности продолжить выполнение, потому что очередь заполнена, что приводит к блокировке всей программы, блок-схема выглядит следующим образом:
Правильный поток выполнения должен быть таким:
1.3 Использование условий
Чтобы решить проблему «притворной смерти» потоков, мы можем использоватьCondition
Давай попробуем воплотить это в жизнь,Condition
Это класс в пакете JUC (java.util.concurrent), и его необходимо использовать.Lock
блокировка для создания,Condition
Предусмотрены три важных метода:
-
await
:вести перепискуwait
метод; -
signal
:вести перепискуnotify
метод; -
signalAll
:notifyAll
метод.
Condition
использовать иwait/notify
Аналогично, сначала устанавливается блокировка, а затем в блокировке выполняются операции ожидания и пробуждения.Condition
Основное использование заключается в следующем:
// 创建 Condition 对象
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 加锁
lock.lock();
try {
// 业务方法....
// 1.进入等待状态
condition.await();
// 2.唤醒操作
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
Немного знаний: правильное использование позы Lock
ЗапомнитьLock
изlock.lock()
метод не может быть помещен вtry
код, еслиlock
метод вtry
Внутри блока кода исключения могут создаваться другими методами, что приводит кfinally
в кодовом блоке,unlock
Чтобы разблокировать разблокированный объект, он вызываетAQS
изtryRelease
метод (в зависимости от конкретного класса реализации), бросаетIllegalMonitorStateException
аномальный.
вернуться к теме
Вернемся к теме этой статьи, если мы используемCondition
Для достижения связи между потоками можно избежать «притворной смерти» программы, т.к.Condition
Можно создать несколько наборов ожидания.Взяв модели производителя и потребителя в этой статье в качестве примера, мы можем использовать два набора ожидания, один для ожидания и пробуждения потребителя, а другой для пробуждения производителя, так что будет нет Производитель будит производителя (производитель может будить только потребителя, а потребитель может будить только производителя), так что весь процесс не будет "симулировать смерть", а процесс его выполнения показан на следующем рисунке :Поняв его основной процесс, давайте посмотрим на конкретный код реализации.
на основеCondition
Код заводской реализации выглядит следующим образом:
class FactoryByCondition {
private int[] items = new int[1]; // 数据存储容器(为了演示方便,设置容量最多存储 1 个元素)
private int size = 0; // 实际存储大小
// 创建 Condition 对象
private Lock lock = new ReentrantLock();
// 生产者的 Condition 对象
private Condition producerCondition = lock.newCondition();
// 消费者的 Condition 对象
private Condition consumerCondition = lock.newCondition();
/**
* 生产方法
*/
public void put() throws InterruptedException {
// 循环生产数据
do {
lock.lock();
while (size == items.length) { // 注意不能是 if 判断
// 生产者进入等待
System.out.println(Thread.currentThread().getName() + " 进入阻塞");
producerCondition.await();
System.out.println(Thread.currentThread().getName() + " 被唤醒");
}
System.out.println(Thread.currentThread().getName() + " 开始工作");
items[0] = 1; // 为了方便演示,设置固定值
size++;
System.out.println(Thread.currentThread().getName() + " 完成工作");
// 唤醒消费者
consumerCondition.signal();
try {
} finally {
lock.unlock();
}
} while (true);
}
/**
* 消费方法
*/
public void take() throws InterruptedException {
// 循环消费数据
do {
lock.lock();
while (size == 0) {
// 消费者阻塞等待
consumerCondition.await();
}
System.out.println("消费者工作~");
size--;
// 唤醒生产者
producerCondition.signal();
try {
} finally {
lock.unlock();
}
} while (true);
}
}
Код реализации двух производителей и одного потребителя выглядит следующим образом:
public class NotifyDemo {
public static void main(String[] args) {
FactoryByCondition factory = new FactoryByCondition();
// 生产者
Thread producer = new Thread(() -> {
try {
factory.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者");
producer.start();
// 生产者 2
Thread producer2 = new Thread(() -> {
try {
factory.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者2");
producer2.start();
// 消费者
Thread consumer = new Thread(() -> {
try {
factory.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者");
consumer.start();
}
}
Результат выполнения программы показан на следующем рисунке:Из вышеприведенных результатов видно, что при использованииCondition
Когда производитель, потребитель и производитель 2 будут выполняться поочередно, и результат выполнения соответствует нашим ожиданиям.
2. Проблемы с производительностью
Выше мы демонстрируемnotify
Когда это вызовет проблему «подвешенной смерти» потока, некоторые друзья должны подумать, что еслиnotify
заменитьnotifyAll
Нить не будет "симулировать смерть".
Такой подход действительно может решить проблему «подвешенной смерти» потоков, но при этом появятся новые проблемы с производительностью.
Используется следующееwait
а такжеnotifyAll
Улучшенный код:
/**
* 工厂类,消费者和生产者通过调用工厂类实现生产/消费功能.
*/
class Factory {
private int[] items = new int[1]; // 数据存储容器(为了演示方便,设置容量最多存储 1 个元素)
private int size = 0; // 实际存储大小
/**
* 生产方法
* @throws InterruptedException
*/
public synchronized void put() throws InterruptedException {
// 循环生产数据
do {
while (size == items.length) { // 注意不能是 if 判断
// 存储的容量已经满了,阻塞等待消费者消费之后唤醒
System.out.println(Thread.currentThread().getName() + " 进入阻塞");
this.wait();
System.out.println(Thread.currentThread().getName() + " 被唤醒");
}
System.out.println(Thread.currentThread().getName() + " 开始工作");
items[0] = 1; // 为了方便演示,设置固定值
size++;
System.out.println(Thread.currentThread().getName() + " 完成工作");
// 唤醒所有线程
this.notifyAll();
} while (true);
}
/**
* 消费方法
* @throws InterruptedException
*/
public synchronized void take() throws InterruptedException {
// 循环消费数据
do {
while (size == 0) {
// 生产者没有数据,阻塞等待
System.out.println(Thread.currentThread().getName() + " 进入阻塞(消费者)");
this.wait();
System.out.println(Thread.currentThread().getName() + " 被唤醒(消费者)");
}
System.out.println("消费者工作~");
size--;
// 唤醒所有线程
this.notifyAll();
} while (true);
}
}
Есть еще два производителя и один потребитель, а код реализации выглядит следующим образом:
public static void main(String[] args) {
Factory factory = new Factory();
// 生产者
Thread producer = new Thread(() -> {
try {
factory.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者");
producer.start();
// 生产者 2
Thread producer2 = new Thread(() -> {
try {
factory.put();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者2");
producer2.start();
// 消费者
Thread consumer = new Thread(() -> {
try {
factory.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "消费者");
consumer.start();
}
Результат выполнения показан на следующем рисунке:Из приведенных выше результатов видно, что:когда мы звонимnotifyAll
Это не приведет к тому, что поток "симулирует смерть", но приведет к пробуждению всех производителей, но, поскольку необходимо выполнить только одну задачу, только один из всех пробужденных производителей выполнит правильную работу, а другой ничего не делать, а затем войти в состояние ожидания.Такое поведение, несомненно, лишнее для всей программы, что только увеличит накладные расходы на планирование потоков, что приведет к снижению производительности всей программы..
НапротивCondition
изawait
а такжеsignal
метод, даже если имеется несколько производителей, программа активирует только одного действительного производителя, как показано на следующем рисунке:Производитель и производитель 2 будут пробуждаться поочередно для работы по очереди, поэтому в этом исполнении нет дополнительных накладных расходов по сравнению сnotifyAll
Производительность всей программы будет значительно улучшена.
Суммировать
В этой статье мы демонстрируем с помощью кода и блок-схемwait
Методы иnotify/notifyAll
В использовании метода есть два основных недостатка, один из которых используется в экстремальных условиях.notify
Это заставит программу «симулировать смерть», а другой — использоватьnotifyAll
Это приведет к снижению производительности, поэтому настоятельно рекомендуется использоватьCondition
класс для реализации.
PS: Некоторые люди могут спросить, почему бы не использовать signalAll и notifyAll Condition для сравнения производительности? И использовать signal и notifyAll для сравнения? Достаточно сказать, зачем использовать signalAll, если это можно сделать с помощью signal? Это как носить короткий рукав в помещении с отоплением в 25 градусов, зачем носить телогрейку?
Подпишитесь на официальный аккаунт «Java Chinese Community», чтобы увидеть больше галантерейных товаров, и проверьте Github, чтобы найти больше интересного:GitHub.com/VIP камень/Али…