Threads
What Are Threads?
Threads are a mechanism defined within Java to execute code in parallel
within a single Java application. There are two ways to execute code in
this manner. The first one is to extend the Thread class. The second is
to implement the Runnable interface. Generally speaking, it is
considered preferable to implement the Runnable interface, but
extending Thread can be more convenient.
public class SimpleThreadExample {
public static void main(String[] args) {
Thread t1 = new HappyThread();
t1.start();
Thread t2 = new Thread(new HappyRunnable());
t2.start();
}
}
class HappyThread extends Thread {
public void run() {
while (true) {
try {
System.out.println("hi!");
Thread.sleep(1000);
} catch (InterruptedException e) {
//do nothing
}
}
}
}
class HappyRunnable implements Runnable {
public void run() {
while (true) {
try {
System.out.println("bonjour!");
Thread.sleep(1000);
} catch (InterruptedException e) {
//do nothing
}
}
}
}
How Are Threads Used?
The basic idea of multi-threading is simple and intuitive. Let's say
you walk into a restaurant. The host greets you and arranges for a
waiter to seat you, tell you about the specials, etc. In the meantime,
the host can continue welcoming new patrons. This notion of performing
two tasks concurrently (welcoming new patrons while serving customers)
is at the heart of what threading is about. One area where threads are
used is Web applications. Within the servlet
api in Java, each time a form is submitted to a servlet, a separate
thread is created to handle the form submission. This serves two
purposes: First of all, it allows additional forms to be processed (for
other users) without having to wait for the first form submission to be
completed. Second of all, it reduces the resources required to process
concurrent form submission. In the old days of Web development (circa
1994), the only way to process form submissions was the CGI (common
gateway interface). The CGI would pass along form submissions to an
executable program (generally-speaking this was a C program in the
early days). Each form submission therefore required creating a whole
new executable process, and once the form was processed, this
executable process was destroyed. Doing this with a C program is bad
enough when you have to process thousands of forms/second; in Java this
would be even worse, since the entire java runtime would have to be
started over and over again, and that can take several seconds. In any
case, "processes" are considered to be "heavyweight" executions
contexts, and threads are "lightweight" executions contexts, since
multiple threads can run concurrently inside of one process (or in one
java runtime in the case of Java). Threads are used extensively in Web
and Enterprise computing to allow efficient access to the same
resources by a large group of clients (a search engine may be a good
example)> Graphical environments are also generally multi-threaded.
This allows certain parts of an application to be waiting for input
(for example data comnig back from a database) without causing the
application to "hang" or "freeze." while it waits. I will also discuss
the use of threads in a remote monitoring and control program I have
worked on.
Thread Synchronization
The following code demonstrates the classic consumer-producer example:
public class ConsumerProducerExample {
public static void main(String[] args) {
Cubbyhole c = new Cubbyhole();
Producer p1 = new Producer(c, 1);
Consumer c1 = new Consumer(c, 1);
p1.start();
c1.start();
}
}
//------------------------------------------------------
class Producer extends Thread {
private Cubbyhole cubbyhole;
private int number;
public Producer(Cubbyhole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (this) {
cubbyhole.put(i);
System.out.println("Producer #" + this.number + " put: " + i);
}
try {
sleep((int)(Math.random() * 100));
} catch (InterruptedException e) { }
}
}
}
//------------------------------------------------------
class Consumer extends Thread {
private Cubbyhole cubbyhole;
private int number;
public Consumer(Cubbyhole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
synchronized (this) {
value = cubbyhole.get();
System.out.println("Consumer #" + this.number + " got: " + value);
}
}
}
}
//------------------------------------------------------
class Cubbyhole {
private int contents;
private boolean empty = true;
public synchronized int get() {
while (empty) {
try {
// wait for Producer to put value
wait();
} catch (InterruptedException e) {
}
}
empty = true;
// notify Producer that value has been retrieved
notifyAll();
return contents;
}
public synchronized void put(int value) {
while (!empty) {
try {
// wait for Consumer to get value
wait();
} catch (InterruptedException e) {
}
}
contents = value;
empty = false;
// notify Consumer that value has been set
notifyAll();
}
}
Why A "while" Loop In put() and get()?
The reason is simple: Say you have multiple producers (this applies in
the same way to consumers). Two producers are waiting to put something
in the cubbyhole. One of them gets access to the lock and successfully
puts an item in. The other waiting producer thread may now also get
into the put method. If you used an "if" statement, then this second
thread would not check to see if the cubbyhole was empty and would
therefore overwrite the contents of the cubbyhole before a consumer had
a chance to retrieve the value put there by the first producer.
Deadlock And Starvation
Multi-threading is a very useful technology but it has its perils. I
won't discuss these problems in detail, but I will outline them so that
you know what to read about if you get involved in writing
multi-threaded code. In truth, most of the time, threading issues are
handled for you by the framework you are using. For example, in Java,
EJB is a technology used for enterprise database development and
distributed computing. The EJB framework handles the
multithreading for you. The servlet framework also hides most of the
issues related to threads for you (although you do have to be aware of
the need to synchronize servelet methods if your servlets are
stateful). So multithreading is not something you are likely to get
involed in any time soon. Still, it is worth having a basic
understanding of the issues.
Deadlock is the problem of threads waiting on resources in a circular
manner, also known as the "deadly embrace." The simplest version of the
deadly embrace is one where threads A and B start at the same time.
Thread B gains exclusive access to a resource (say a database record).
thread A blocks waiting for thread B to release this resource. However,
having acquired the database record, thread B blocks as well, because
it needs to access thread A (which is blocked) in order to complete.
Neither thread can now complete. In the general case, deadlock
detection is very difficult (though not in this simple case).
Starvation is the notion that a thread will not be granted running time
because higher priority threads are always getting in the way. The
problem is that the thread may in fact be lower priority than any of
the other threads individually, but as a whole, this low priority
thread must run eventually.
It is important to write multi-threaded programs so that threads are
not starved entirely, even if they are lower priority. These two issues
of deadlock and starvation make writing complex multithreaded
applications very difficult. This is why you should generally avoid
using threads if at all possible -- take advantage of frameworks that
handle these issues for you. If you do wind up writing multithreaded
code, make sure you isolate resource-sharing issues in a very clear way
and that you test your code extensively under load.