Function of condition
In fact, there are many usage scenarios of condition, which can be used in concurrent scenarios involving condition judgment, such as:
- Judge whether the queue is full or empty in the ArrayBlockingQueue of the blocking queue
- Block and wake up all threads in CyclicBarrier
- Judgment of blocking access queue data in DelayQueue
- Condition judgment of awaitTermination method in ThreadPoolExecutor of thread pool
How to use condition?
When using synchronized, we can use wait(), notify(), and notifyAll() methods to schedule threads, while condition provides a similar method: wait(),signal(),signalAll function, and can more finely control the waiting range. As mentioned above, jdk uses a lot of ReentrantLock and condition to achieve thread scheduling
Let's look at the most common use of condition: the model of production and consumer:
public class ConditionTest { LinkedList<String> lists = new LinkedList<>(); Lock lock = new ReentrantLock(); //Condition judgment of whether the set is full Condition fullCondition = lock.newCondition(); //Condition judgment of whether the set is empty Condition emptyCondition = lock.newCondition(); //producer private void product(){ lock.lock(); try { //Suppose the set size is 10 while (lists.size() == 10){ System.out.println("list is full"); fullCondition.await(); } //Produce a 5-bit random string String randomString = getRandomString(5); lists.add(randomString); System.out.println(String.format("product %s size %d %s",randomString,lists.size(),Thread.currentThread().getName())); //Inform consumers that they can consume emptyCondition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } //consumer private String consume(){ lock.lock(); try{ while (lists.size() == 0){ System.out.println("list is empty"); emptyCondition.await(); } String first = lists.removeFirst(); //Inform the producer that production is ready fullCondition.signalAll(); return first; } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } return null; } /** * Generate random string * @param length * @return */ public static String getRandomString(int length){ String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random=new Random(); StringBuffer sb=new StringBuffer(); for(int i=0;i<length;i++){ int number=random.nextInt(62); sb.append(str.charAt(number)); } return sb.toString(); } public static void main(String[] args) { ConditionTest test = new ConditionTest(); ExecutorService executorService = Executors.newCachedThreadPool(); //The number of threads controls whether consumption is fast or production is fast for(int i = 0;i<2;i++){ executorService.submit(()->{ System.out.println(Thread.currentThread().getName()); while (true){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } test.product(); } }); } for(int k = 0;k<1;k++){ executorService.submit(()->{ System.out.println("cousumestart"); while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } String consume = test.consume(); System.out.println("consume " + consume+ " "+Thread.currentThread().getName() ); } }); } //Wait for input and block the main thread without exiting try { new BufferedReader(new InputStreamReader(System.in)).readLine(); } catch (IOException e) { e.printStackTrace(); } }
//Partial output log product qeV0r size 7 pool-1-thread-1 product xEUkA size 8 pool-1-thread-2 consume P5Je1 pool-1-thread-3 product rQS1D size 8 pool-1-thread-1 product QcEtf size 9 pool-1-thread-2 consume 2q7Fc pool-1-thread-3 product Z5rBg size 9 pool-1-thread-1 consume UBxBD pool-1-thread-3 product Tr5q2 size 9 pool-1-thread-2 product HXBdE size 10 pool-1-thread-1 list is full consume aYDNR pool-1-thread-3 product ukjnk size 10 pool-1-thread-2 list is full consume LBEdA pool-1-thread-3 product iK28H size 10 pool-1-thread-2 list is full list is full
You can see that there are two producer threads and one consumer thread. The speed of production and consumption is the same. Use thread Sleep control,
The production speed is higher than the consumption speed. When the last set of elements reaches 10, the producer calls fullcondition await(); Blocking, only consumers pass fullcondition after consumption signalAll(); Inform the producer to continue production
Similarly, if the number of consumer threads is added to make the consumption speed faster than that of production, emptycondition will be called when the collection is empty await(); Blocking, the producer calls back after production with emptycondition signalAll(); Inform consumers to continue production
Compared with the wait() and notifyAll() methods of the object, they are judged separately under different conditions, with smaller granularity and more accurate range of wake-up threads
Let's take another example of ArrayBlockingQueue, which blocks the acquisition of queue data for a period of time, and returns NULL if it cannot be obtained:
public E poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) { if (nanos <= 0) return null; //notEmpty is a condition from lock new nanos = notEmpty.awaitNanos(nanos); } return dequeue(); } finally { lock.unlock(); } }
There are many usage scenarios for condition. Let's take a look at the implementation principle of condition. First, condition needs to implement the class in AbstractQueuedSynchronizer
Analysis of condition principle
We know that AQS maintains a queue to control the execution of threads, and condition uses another waiting queue to judge conditions. Condition must be used after AQS acquire s the lock and calls condition Await () method will add a node to the condition queue. After calling signal() or signalAll(), move the node out of the condition waiting queue and put it into the lock waiting queue to compete for the lock. After taking the lock, continue to execute the subsequent logic.