Please, stop waiting and notify!

Condition is a thread communication method provided in JDK 1.5 to replace wait and notify. Then someone will ask: why can't wait and notify be used? Man, I use it well. Don't worry, brother. Let me tell you in detail

There are two reasons why condition is recommended instead of wait and notify in object:

Next, we will use code and flow chart to demonstrate the above two situations.

1. Notify thread "fake death"

The so-called thread "fake death" means that when you use notify to wake up multiple waiting threads, you accidentally wake up a thread that is not "ready", resulting in the whole program entering a blocked state and unable to continue execution.

Taking the classic case producer and consumer model in multithreaded programming as an example, let's first demonstrate the problem of thread "fake death".

1.1 normal version

Before demonstrating the problem of thread "fake death", we first use wait and notify to implement a simple producer and consumer model. In order to make the code more intuitive, I write a super simple implementation version here. Let's first create a factory class. The factory class contains two methods, one is the (storage) method of circular production data, and the other is the (extraction) method of circular consumption data. The implementation code is as follows.

/**
 * 工厂类,消费者和生产者通过调用工厂类实现生产/消费
 */
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);
    }
}

Next, let's create two threads. One is the producer calling the put method, and the other is the consumer calling the take method. The implementation code is as follows:

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();
    }
}

The results are as follows:

1.2 thread "fake death" version

When there is only one producer and one consumer, there will be no problem with the wait and notify methods. However, when there are two producers, the thread will "fake death". The implementation code of the program is as follows:

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();
    }
}

The program execution results are as follows:

Analysis of thread "fake death"

Let's mark the execution steps of the above procedures and get the following results:

1.3 using condition

In order to solve the "false death" problem of threads, we can use condition to try to implement it. Condition is a class under the JUC (Java. Util. Concurrent) package and needs to be created with lock lock. Condition provides three important methods:

The use of condition is similar to that of wait / notify. It first obtains the lock, and then performs wait and wake-up operations in the lock. The basic usage of condition is as follows:

// 创建 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();
}

Tips: Lock's correct posture

Remember lock The lock () method cannot be put into the try code. If the lock method is within the try code block, other methods may throw exceptions, which may cause unlock to unlock the unlocked object in the finally code block. It will call the tryrelease method of AQS (depending on the specific implementation class) and throw the illegalmonitorstateexception exception.

Return to theme

Returning to the topic of this article, if we use condition to realize thread communication, we can avoid the "fake death" of the program, because condition can create multiple waiting sets. Taking the producer and consumer model in this article as an example, we can use two waiting sets, one for the consumer's waiting and waking up, and the other for waking up the producer, In this way, the producer will not wake up the producer (the producer can only wake up the consumer, and the consumer can only wake up the producer). In this way, the whole process will not "fake death". Its execution process is shown in the following figure:

The factory implementation code based on condition is as follows:

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);
    }
}

The implementation codes of two producers and one consumer are as follows:

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();
            }
        },"生产者2");
        producer2.start();

        // 消费者
        Thread consumer = new Thread(() -> {
            try {
                factory.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者");
        consumer.start();
    }
}

The execution results of the program are shown in the following figure:

2. Performance issues

When we demonstrated that notify can cause thread "fake death", some friends must think that if notify is replaced with notifyAll thread, it will not "fake death".

This approach can indeed solve the problem of thread "fake death", but at the same time, new performance problems will come. Empty words have no basis, and they will be directly displayed in the code.

Here is the improved code using wait and 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 判断
                // 存储的容量已经满了,设置固定值
            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);
    }
}

It is still two producers plus one consumer, and the implementation code is as follows:

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();
}

The execution results are shown in the following figure:

In contrast to the await and signal methods of condition, even if there are multiple producers, the program will only wake up one effective producer to work, as shown in the following figure:

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>