1+ package com .rampatra .threads ;
2+
3+ import java .util .concurrent .ExecutorService ;
4+ import java .util .concurrent .Executors ;
5+ import java .util .concurrent .TimeUnit ;
6+ import java .util .concurrent .locks .Condition ;
7+ import java .util .concurrent .locks .Lock ;
8+ import java .util .concurrent .locks .ReentrantLock ;
9+
10+ /**
11+ * Problem Description: A simple Producer/Consumer using the Lock and Condition Api pattern. For the language primitive,
12+ * i.e, synchronize and wait/notify pattern, please see {@link ProducerConsumerUsingWaitNotify}.
13+ * <p>
14+ * <p>
15+ * There are a few advantages of going with the Lock Api pattern instead of the language primitive synchronize and
16+ * wait/notify pattern:
17+ * <p>
18+ * - Can be interrupted which means that the application won't continue to run forever in weird situations. Consider this
19+ * example, what happens if the Consumer starts first and there are no elements to consume and the Producer also fails
20+ * due to some exception. In wait/notify the Consumer would stall forever. You would have to restart the JVM to get
21+ * rid of this. However, with Lock api, you can use {@link Lock#lockInterruptibly()}.
22+ * <p>
23+ * - Timed lock acquisition. You can try to acquire a lock and if it is not instantly available then do something else.
24+ * See {@link Lock#tryLock()} to learn more. You can also wait for a certain amount of time before giving up with the
25+ * {@link Lock#tryLock(long, TimeUnit)} method. This isn't possible with the primitive pattern.
26+ * <p>
27+ * - A fair Lock generates a fair Condition. Fair here means the first thread in the waiting queue will be picked first
28+ * by the scheduler. This is a costly operation so use it only when necessary.
29+ *
30+ * @author rampatra
31+ * @since 2019-07-10
32+ */
33+ public class ProducerConsumerUsingLockApi {
34+
35+ private static int currSize = 0 ;
36+ private static int totalSize = 10 ;
37+ private static int [] buffer = new int [totalSize ];
38+ private static Lock lock = new ReentrantLock ();
39+ private static Condition isEmpty = lock .newCondition ();
40+ private static Condition isFull = lock .newCondition ();
41+
42+ static class Producer {
43+ static void produce () {
44+ try {
45+
46+ lock .lock ();
47+ while (currSize >= totalSize ) {
48+ isFull .await ();
49+ }
50+ buffer [currSize ++] = 1 ;
51+ isEmpty .signal ();
52+
53+ } catch (InterruptedException e ) {
54+ e .printStackTrace ();
55+ } finally {
56+ lock .unlock ();
57+ }
58+ }
59+ }
60+
61+ static class Consumer {
62+ static void consume () {
63+ try {
64+
65+ lock .lock ();
66+ while (currSize <= 0 ) {
67+ isEmpty .await ();
68+ }
69+ System .out .println (buffer [--currSize ]);
70+ isFull .signal ();
71+
72+ } catch (InterruptedException e ) {
73+ e .printStackTrace ();
74+ } finally {
75+ lock .unlock ();
76+ }
77+ }
78+ }
79+
80+ public static void main (String [] args ) throws InterruptedException {
81+
82+ ExecutorService executorService = Executors .newFixedThreadPool (2 );
83+
84+ Runnable producerTask = () -> {
85+ for (int i = 0 ; i < 1000 ; i ++) {
86+ Producer .produce ();
87+ }
88+ };
89+
90+ Runnable consumerTask = () -> {
91+ for (int i = 0 ; i < 1000 ; i ++) {
92+ Consumer .consume ();
93+ }
94+ };
95+
96+ executorService .submit (producerTask );
97+ executorService .submit (consumerTask );
98+
99+ executorService .awaitTermination (3000 , TimeUnit .MILLISECONDS );
100+
101+ // as produce() and consume() are called equal number of times, this should be zero in the end
102+ System .out .println ("Buffer Size: " + currSize );
103+
104+ executorService .shutdown ();
105+ }
106+ }
0 commit comments