Java Concurrent Programming - Condition

Keywords: JDK Attribute

Before looking at Condition s, let's look at the following example:

Factory class, used to store and take out goods:

public class Depot {
    private int depotSize;     //Warehouse size
    private Lock lock;         //exclusive lock
    
    public Depot(){
        depotSize = 0;
        lock = new ReentrantLock();
    }
    
    /**
     * Goods warehousing
     * @param value
     */
    public void put(int value){
        try {
            lock.lock();
            depotSize += value;
            System.out.println(Thread.currentThread().getName() + " put " + value +" ----> the depotSize: " + depotSize);
        } finally{
            lock.unlock();
        }
    }
    
    /**
     * Goods out of stock
     * @param value
     */
    public void get(int value){
        try {
            lock.lock();
            depotSize -= value;
            System.out.println(Thread.currentThread().getName() + " get " + value +" ----> the depotSize: " + depotSize);
        } finally{
            lock.unlock();
        }
    }
}

Producers, produce goods, add goods to warehouses:

public class Producer {
    private Depot depot;
    
    public Producer(Depot depot){
        this.depot = depot;
    }
    
    public void produce(final int value){
        new Thread(){
            public void run(){
                depot.put(value);
            }
        }.start();
    }
}

Consumers, consumer goods, take goods out of warehouses:

public class Customer {
    private Depot depot;
    
    public Customer(Depot depot){
        this.depot = depot;
    }
    
    public void consume(final int value){
        new Thread(){
            public void run(){
                depot.get(value);
            }
        }.start();
    }
}

Test class:

public class Test {
    public static void main(String[] args) {
        Depot depot = new Depot();
        
        Producer producer = new Producer(depot);
        Customer customer = new Customer(depot);
        
        producer.produce(10);
        customer.consume(5);
        producer.produce(20);
        producer.produce(5);
        customer.consume(35);
    }
}

Operation results:

Thread-0 put 10 ----> the depotSize: 10
Thread-1 get 5 ----> the depotSize: 5
Thread-2 put 20 ----> the depotSize: 25
Thread-3 put 5 ----> the depotSize: 30
Thread-4 get 35 ----> the depotSize: -5

The running result of the program is correct, put10, get5, put20, put5, get35. The program runs very correctly, but in real life, there are two mistakes in this example:

First, the capacity of the warehouse is limited. We can't add goods to the warehouse without restriction.

Secondly, the capacity of warehouse is impossible to be negative, but the final result is - 5, which is in conflict with reality.

For the above two mistakes, how to solve them? It's Condition's turn to shine.

Condition

From the previous blogs, we know that Lock provides a more powerful and flexible locking mechanism than synchronized, which to some extent replaces the use of synchronized mode. Conditions are literally conditions. For threads, it provides a meaning for threads to suspend a state (conditional Condition) until it is notified by another thread of true.

For Condition, this is explained in the JDK API:

Condition decomposes Object Monitor methods (wait, notify and notify all) into distinct objects to provide multiple wait-set s for each object by combining these objects with any Lock implementation. Lock replaces the use of synchronized methods and statements, and Condition replaces the use of Object monitor methods.

Conditions (also known as conditional queues or conditional variables) provide a meaning for threads to suspend a state condition (i.e., to "wait" until it can be notified by another thread of true. Because access to this shared state information occurs in different threads, it must be protected, so some form of lock is associated with this condition. The main attribute of waiting to provide a condition is to release the associated lock atomically and suspend the current thread, as Object.wait does.

Condition instances are essentially bound to a lock. To obtain a Condition instance for a particular Lock instance, use its newCondition() method. Next, we solve the above problem through Conditions: here we only change the code of Depot in the warehouse:

public class Depot {
    private int depotSize;     //Warehouse size
    private Lock lock;         //exclusive lock    
    private int capaity;       //Warehouse capacity
    
    private Condition fullCondition;        
    private Condition emptyCondition;
    
    public Depot(){
        this.depotSize = 0;
        this.lock = new ReentrantLock();
        this.capaity = 15;
        this.fullCondition = lock.newCondition();
        this.emptyCondition = lock.newCondition();
    }
    
    /**
     * Goods warehousing
     * @param value
     */
    public void put(int value){
        lock.lock();
        try {
            int left = value;
            while(left > 0){
                //When stocks are full, "producers" wait for "consumers" to consume
                while(depotSize >= capaity){
                    fullCondition.await();
                }
                //Acquire the actual inventory: expected inventory (warehouse existing inventory + production quantity) > warehouse capacity? Warehouse capacity - warehouse existing inventory: production quantity
                //                  depotSize   left   capaity  capaity - depotSize     left
                int inc = depotSize + left > capaity ? capaity - depotSize : left; 
                depotSize += inc;
                left -= inc;
                System.out.println(Thread.currentThread().getName() + "----Quantity to be warehoused: " + value +";;Actual storage quantity:" + inc + ";;Quantity of warehouse goods:" + depotSize + ";;No storage quantity:" + left);
            
                //Notify consumers that they can consume
                emptyCondition.signal();
            }
        } catch (InterruptedException e) {
        } finally{
            lock.unlock();
        }
    }
    
    /**
     * Goods out of stock
     * @param value
     */
    public void get(int value){
        lock.lock();
        try {
            int left = value;
            while(left > 0){
                //The warehouse is empty and the consumer is waiting for the producer to produce the goods.
                while(depotSize <= 0){
                    emptyCondition.await();
                }
                //Actual Consumption Warehouse Inventory < Quantity to Consume? Warehouse Inventory Quantity: Quantity to Consume
                int dec = depotSize < left ? depotSize : left;
                depotSize -= dec;
                left -= dec;
                System.out.println(Thread.currentThread().getName() + "----Quantity to be consumed:" + value +";;Quantity of actual consumption: " + dec + ";;Existing warehouse quantity:" + depotSize + ";;How many items are not consumed?" + left);
            
                //Notify the producer that it is ready to produce
                fullCondition.signal();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{
            lock.unlock();
        }
    }
}

Test:

public class Test {
    public static void main(String[] args) {
        Depot depot = new Depot();
        
        Producer producer = new Producer(depot);
        Customer customer = new Customer(depot);
        
        producer.produce(10);
        customer.consume(5);
        producer.produce(15);
        customer.consume(10);
        customer.consume(15);
        producer.produce(10);
    }
}

Operation results:

Thread-0 - The quantity to be put into storage: 10. The actual quantity to be put into storage: 10. The quantity of warehouse goods: 10. No quantity to be put into storage: 0.
Thread-1 - Quantity of Consumption: 5) Quantity of Actual Consumption: 5) Quantity of Existing Warehouses: 5) How many items are not consumed: 0
 Thread-4 - Quantity of Consumption: 15; Quantity of Actual Consumption: 5; Existing Quantity of Warehouse: 0; How Many Goods Are Not Consumed: 10
 Thread-2 - The number of warehouses to be stored: 15. Actual number of warehouses: 15. Warehouse goods: 15. No number of warehouses: 0.
Thread-4 - Quantity of Consumption: 15; Quantity of Actual Consumption: 10; Existing Quantity of Warehouse: 5; How Many Goods Are Not Consumed: 0
 Thread-5 - The number of warehouses to be stored: 10. Actual number of warehouses: 10. Warehouse goods: 15. No number of warehouses: 0.
Thread-3 - Quantity of Consumption: 10. Quantity of Actual Consumption: 10. Existing Quantity of Warehouse: 5. How Many Goods Are Not Consumed: 0

In Conditions, wait() is replaced by await(), notify() is replaced by signal(), and notifyAll() is replaced by signalAll(). Conditions can be implemented for our traditional Object method.

Posted by nominator on Mon, 03 Jun 2019 21:05:04 -0700