Join, interrupt and volatile
Join
A join causes one thread to wait for another thread to complete. The joining thread goes into the WAITING state until the waiting thread finishes. If the waiting thread has already finished, the join does nothing.
public class JoinThread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("starting");
spend(1500);
log("ending");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "child");
log("starting");
thread.start();
log("started");
thread.join();
log("joined");
}
}
Interrupted
An interrupt is an indication to a thread that it should stop doing what it is doing and do something else. Threads have a flag indicating whether they have been interrupted or not. There are two ways to check if a thread has been interrupted:
- That the thread makes frequent calls to methods that throw InterruptedException. For example, Thread.sleep(). Also useful if the interrupt occurred before sleep().
- Have the thread frequently check Thread.currentThread().isInterrupted().
Once the check is done and the break is detected, the thread's break flag is set back to `false' and a decision must be made about what to do. Usually what it will do is terminate its execution, but it doesn't have to. If nothing is done, the thread will continue to run.
public class Interrupt1Thread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("starting");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
log("interrupted!");
}
log("ending");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "child");
log("starting");
thread.start();
log("started");
thread.interrupt();
thread.join();
log("joined");
}
}
public class Interrupt2Thread {
static class MyRunnable implements Runnable {
@Override
public void run() {
log("starting");
while (!Thread.currentThread().isInterrupted());
log("ending");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable(), "child");
log("starting");
thread.start();
log("started");
rest(1500);
thread.interrupt();
thread.join();
log("joined");
}
}
Shared status
A thread can be terminated by sharing a variable that one thread modifies and the other thread reads. In Java, the variable must be defined as `volatile', indicating that changes made in one thread are visible in the rest. Alternatively, use safe objects created by us (sync mechanisms) or from a safe library (eg Java's atomic).
The volatile
keyword should only be used if one thread is writing and the other (or others) are reading. If multiple threads are writing and reading, this needs to be handled as critical sections.
public class VolatileThread {
static class SharedObject {
boolean done; // volatile keyword needed
}
static class MyRunnable1 implements Runnable {
SharedObject so;
MyRunnable1(SharedObject so) {
this.so = so;
}
@Override
public void run() {
spend(1500);
so.done = true;
}
}
static class MyRunnable2 implements Runnable {
SharedObject so;
MyRunnable2(SharedObject so) {
this.so = so;
}
@Override
public void run() {
boolean done = false;
while (!done) {
done = so.done;
}
}
}
public static void main(String[] args) throws InterruptedException {
SharedObject so = new SharedObject();
Thread t1 = new Thread(new MyRunnable1(so));
Thread t2 = new Thread(new MyRunnable2(so));
t1.start();
t2.start();
t1.join();
log("joined 1 with " + so.done);
t2.join();
log("joined 2 with " + so.done);
}
}