Thread
object to
Executor.execute
? Would such an invocation make
sense? Why or why not?
Answer:
Thread
implements the Runnable
interface, so you can pass an instance of Thread
to
Executor.execute
. However it doesn't make sense to
use Thread
objects this way. If the object is
directly instantiated from Thread
, its
run
method doesn't do anything. You can define a
subclass of Thread
with a useful run
method — but such a class would implement features that the
executor would not use.
BadThreads.java
:
public class BadThreads { static String message; private static class CorrectorThread extends Thread { public void run() { try { sleep(1000); } catch (InterruptedException e) {} //Key statement 1: message = "Mares do eat oats."; } } public static void main(String args[]) throws InterruptedException { (new CorrectorThread()).start(); message = "Mares do not eat oats."; Thread.sleep(2000); //Key statement 2: System.out.println(message); } }
The application should print out "Mares do eat oats." Is it
guaranteed to always do this? If not, why not? Would it help to change
the parameters of the two invocations of Sleep
?
How would you guarantee that all changes to message
will be visible to the main thread?
Solution: The program will almost always print out "Mares do eat oats." However, this result is not guaranteed, because there is no happens-before relationship between "Key statement 1" and "Key statment 2". This is true even if "Key statement 1" actually executes before "Key statement 2" — remember, a happens-before relationship is about visibility, not sequence.
There are two ways you can guarantee that all changes to
message
will be visible to the main thread:
CorrectorThread
instance. Then invoke
join
on that instance before referring to
message
message
in an object with
synchronized methods. Never reference message
except through those methods.
Both of these techniques establish the necessary happens-before
relationship, making changes to message
visible.
A third technique is to simply declare message
as
volatile
. This guarantees that any write to
message
(as in "Key statement 1") will have a happens-before relationship with
any subsequent reads of message
(as in "Key
statement 2"). But it does not guarantee that "Key statement 1"
will literally happen before "Key statement 2". They will
probably happen in sequence, but because of scheduling
uncertainities and the unknown granularity of sleep
,
this is not guaranteed.
Changing the arguments of the two sleep
invocations
does not help either, since this does nothing to guarantee a
happens-before relationship.
Drop
class.
Solution:
The
java.util.concurrent.BlockingQueue
interface defines a get
method that blocks if the queue
is empty, and a put
methods that blocks if the queue is
full. These are effectively the same operations defined by
Drop
— except that Drop
is not a
queue! However, there's another way of looking at Drop: it's a queue
with a capacity of zero. Since there's no room in the queue for
any elements, every get
blocks until the
corresponding take
and every take
blocks
until the corresponding get
. There is an implementation
of BlockingQueue
with precisely this behavior:
java.util.concurrent.SynchronousQueue
.
BlockingQueue
is almost a drop-in replacement for
Drop
. The main problem in
is that with Producer
BlockingQueue
, the put
and
get
methods throw InterruptedException
. This
means that the existing try
must be moved up a level:
import java.util.Random; import java.util.concurrent.BlockingQueue; public class Producer implements Runnable { private BlockingQueue<String> drop; public Producer(BlockingQueue<String> drop) { this.drop = drop; } public void run() { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; Random random = new Random(); try { for (int i = 0; i < importantInfo.length; i++) { drop.put(importantInfo[i]); Thread.sleep(random.nextInt(5000)); } drop.put("DONE"); } catch (InterruptedException e) {} } }
Consumer
:
import java.util.Random; import java.util.concurrent.BlockingQueue; public class Consumer implements Runnable { private BlockingQueue<String> drop; public Consumer(BlockingQueue<String> drop) { this.drop = drop; } public void run() { Random random = new Random(); try { for (String message = drop.take(); ! message.equals("DONE"); message = drop.take()) { System.out.format("MESSAGE RECEIVED: %s%n", message); Thread.sleep(random.nextInt(5000)); } } catch (InterruptedException e) {} } }
ProducerConsumerExample
,
we simply change the declaration for the drop
object:
import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; public class ProducerConsumerExample { public static void main(String[] args) { BlockingQueue<String> drop = new SynchronousQueue<String> (); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); } }