|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 7
CondDemo's main() method instantiates Shared, Producer, and Consumer. It passes the Shared instance to the Producer and Consumer thread instance constructors and starts these threads.
The Producer and Consumer constructors are invoked on the main thread. Because the Shared instance is also accessed on the producer and consumer threads, it's necessary for this instance to be visible to them, especially
when these threads run on different cores. Within each of Producer and Consumer, I accomplish this task by declaring s final. I could have declared this field volatile, but volatile suggests that there will be further writes to the field and s isn't supposed to change after being initialized.
Shared's constructor creates a lock (lock = new ReentrantLock();) and an associated condition (condition = lock.newCondition();). This lock is made available to the producer and consumer threads via the Lock getLock() method.
The producer thread always invokes the void setSharedChar(char c) method to generate a new character. This method locks the previously created lock object and then enters a while loop that repeatedly tests variable available -- this variable is true when a produced character hasn't yet been consumed.
As long as available is true, the producer invokes the condition's await() method to wait for available to become false. The consumer will signal the condition to wake up the producer when it has consumed the character. (A loop
is used instead of an if statement because spurious wakeups are possible and available might still be true.)
After exiting the loop, the producer records the new character, assigns true to available to indicate that a new character is available for consumption, and signals the condition to wake up a waiting consumer. Lastly,
it unlocks the lock and returns from setSharedChar().
Why am I locking the get/print and set/print code blocks? Without this locking, you might observe consuming messages before producing messages, even though characters are produced before they're consumed. Locking these blocks prevents this strange output order.
The behavior of the consumer thread in the char getSharedChar() method is similar.
The mechanics of the producer and consumer threads are simpler. Each run() method first locks the lock, then sets or gets a character and outputs a message, and unlocks the lock. (I didn't use the
try/finally idiom because an exception isn't thrown from this context.)
Compile CondDemo.java and run the application. You should observe the following output:
A produced by producer.
A consumed by consumer.
B produced by producer.
B consumed by consumer.
C produced by producer.
C consumed by consumer.
D produced by producer.
D consumed by consumer.
E produced by producer.
E consumed by consumer.
F produced by producer.
F consumed by consumer.
G produced by producer.
G consumed by consumer.
H produced by producer.
H consumed by consumer.
I produced by producer.
I consumed by consumer.
J produced by producer.
J consumed by consumer.
K produced by producer.
K consumed by consumer.
L produced by producer.
L consumed by consumer.
M produced by producer.
M consumed by consumer.
N produced by producer.
N consumed by consumer.
O produced by producer.
O consumed by consumer.
P produced by producer.
P consumed by consumer.
Q produced by producer.
Q consumed by consumer.
R produced by producer.
R consumed by consumer.
S produced by producer.
S consumed by consumer.
T produced by producer.
T consumed by consumer.
U produced by producer.
U consumed by consumer.
V produced by producer.
V consumed by consumer.
W produced by producer.
W consumed by consumer.
X produced by producer.
X consumed by consumer.
Y produced by producer.
Y consumed by consumer.
Z produced by producer.
Z consumed by consumer.
You'll occasionally encounter a situation where data structures are read more often than they're modified. The Locking framework has a read-write locking mechanism for these situations that yields both greater concurrency when reading and the safety of exclusive access when writing.
Previous articles in Java 101: The next generation
java.util.concurrent, with Jeff Friesen's detailed introduction to the Executor framework, synchronizer types, and the Java Concurrent Collections
package.
Concurrency tutorials on JavaWorld:
java.util.concurrent to work around deadlock and similar threading pitfalls.
CountDownLatch and Executors.newFixedThreadPool concurrency utilities.
java.util.concurrent classes were used to optimize thread use for faster performance in a real-world application.
ExecutorService class with ForkJoinPool in a web crawler.