An active object is an object associated with a thread. When such an object invokes a method (one of its own or a method of some other object) it runs the code for this method in its associated thread.
An object can be associated with a thread either by extending Java's Thread class and overriding the run method, or by implementing Java's Runnable interface and encapsulating this object by an instance of Thread.
Here are a few relevant declarations from java.lang:
interface Runnable {
public void run();
}
class Thread implements Runnable {
private Runnable slave;
public void start() {
if (slave != null) slave.run();
else run();
}
public void run() { /* no-op */ } //
overridable
// etc.
}
class Slave extends Thread {
long count = 0;
public void run() {
while(true) {
count++;
yield(); // be cooperative
}
}
}
public class Master {
public static void main(String[] args)
{
Slave slave = new Slave();
slave.start();
System.out.println("Press
Enter to request count");
while(true) {
try {
System.in.read(); // wait for request
} catch(Exception e) {}
System.out.println("count = " + slave.count);
}
}
}
Press Enter to request count
count = 4097065
count = 4097065
count = 6691066
count = 6691066
count = 9177059
count = 9177059
count = 12050482
count = 12050482
count = 15805407
count = 15805407
class Slave implements Runnable {
long count = 0;
public void run() {
while(true) {
count++;
Thread.yield(); // be
cooperative
}
}
}
public class Master {
public static void main(String[] args)
{
Slave slave = new Slave();
new Thread(slave).start(); //
encapsulate slave in a thread
System.out.println("Press
Enter to request count");
while(true) {
try {
System.in.read(); // wait for request
} catch(Exception e) {}
System.out.println("count = " + slave.count);
}
}
}
A Java thread can have one of three priorities:
Thread.MAX_PRIORITY
Thread.NORM_PRIORITY // the default
Thread.MIN_PRIORITY
Response time slows considerably if the slave has a higher priority than the master:
Slave slave = new Slave();
slave.setPriority(Thread.MAX_PRIORITY);
slave.start();
This happens because the thread queue is actually a priority queue ordered by thread priorities. Thus, the slave cuts in front of the master making the master less responsive. This problem can be mitigated to some degree by having the slave sleep instead of yield:
class Slave extends Thread {
long count = 0;
public void run() {
while(true) {
count++;
try { Thread.sleep(1000); }
// sleep for 1 sec.
catch(Exception e) {}
}
}
}
Of course only one thread at a time is running. We can learn the identity of that thread by calling the static method:
Thread.currentThread()
Here are a few additional operations that apply to the current thread:
Thread.yield()
Thread.holdsLock()
Thread.interrupted()
Thread.dumpStack()
Thread.sleep(msecs)
Threads can create and run other threads, thus, a collection of threads can often be organized into a parent-child hierarchy called a thread tree. Child threads often perform tasks on behalf of their parent, thus, there is a master-slave relationship between parent and child.
The root of a thread tree-- hence the ultimate master-- is the thread that runs main(). This is often called the main, user-interface, or application thread. The UI thread is automatically equipped with a thread by the JVM, thus it is not necessary to declare a relationship to the Thread class for this thread.
In a typical application, the main thread creates and starts a few slaves, then kicks back waiting for them to die. If the parent dies prematurely, then all of the descendants die prematurely too, so it's important for the parent to wait. Waiting for a thread to die can be done by calling the thread's join method:
try {
slaves.join();
} catch(InterruptedException ie) {
System.err.println(ie.getMessage());
}
This causes the caller to block execution until the slave dies or until it receives an interrupt.
Here's a template for a typical UI master thread:
public class Master {
public static void main(String[] args)
{
int slaveCount = 10;
Thread[] slaves = new
Thread[slaveCount];
// create slaves:
for(int i = 0; i < slaveCount;
i++) {
slaves[i] = new Slave("slave
" + i);
}
// start slaves:
for(int i = 0; i < slaveCount;
i++) {
slaves[i].start();
}
// wait for slaves to die:
for(int i = 0; i < slaveCount;
i++) {
try {
slaves[i].join();
} catch(InterruptedException ie)
{
System.err.println(ie.getMessage());
} finally {
System.out.println(slaves[i].getName()
+ " has died");
}
}
System.out.println("The master
will now die ... ");
}
}
Notice that slaves can be given names.
The following thread estimates the value of pi:
class Estimator extends Thread {
double myPi = 4;
public void run() {
System.out.println("computing
pi");
int sign = -4;
for(int i = 3; i < 100000; i +=
2) {
myPi += sign * 1.0/i;
sign *= -1;
}
System.out.println("done");
}
}
The master thread creates and starts an estimator, but dies before the estimator completes its task:
public class CalcPi {
public static void main(String[] args)
{
Estimator est = new Estimator();
est.start();
System.out.println("waiting for
est");
System.out.println("pi = "
+ est.myPi);
}
}
Here's the (incorrect) output produced:
waiting for est
pi = 4.0
computing pi
done
We can remedy the situation if the mater enters a busy-waiting loop until the estimator thread dies:
public class CalcPi {
public static void
main(String[] args) {
Estimator est = new Estimator();
est.start();
System.out.println("waiting for
est");
while (est.isAlive()); //
busy-waiting
System.out.println("pi = "
+ est.myPi);
}
}
Here's the output produced:
waiting for est
computing pi
done
pi = 3.1415726535897814
The trouble with this solution is that the master wastes processor time busy waiting for the slave to terminate. We can remedy this problem by having the master thread invoke the join method of the estimator thread:
public class CalcPi {
public static void main(String[] args)
{
Estimator est = new Estimator();
est.start();
System.out.println("waiting for
est");
try {
est.join();
} catch(InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("pi = "
+ est.myPi);
}
}
This puts the master in a blocked state waiting for the estimator to terminate.
Every thread has an associated state that changes throughout the thread's lifetime:
class Thread {
public static final int CREATED = 0,
READY = 1, RUNNING = 2, ... ;
private int state = Thread.CREATED;
// etc.
}
State transitions are caused by events such as interrupts, preemptions, and timeouts, or by explicit method invocations. Not all state transitions are possible. Here's a partial state diagram showing some of the states and the transitions between them: