Here is the one of the solution to solve the given problem. It uses wait
to wait the current thread after printing one letter and notify
the other thread to print its letter, and same cycle repeats. The concept of Thread wait
and notify
is very well explained here.
Short description:
To wait
or notify
any thread, it (invoking thread) must acquire the lock on any common object. In below example, each thread is acquiring the lock on this
(self) (via synchronized run
method) to wait
and on opponent
to notify it (the another thread).
public class App implements Runnable {
char c;
App opponent;
boolean go;
public App(char c, boolean go) {
this.c = c;
this.go = go;
}
public void setOpponent(App opponent) {
this.opponent = opponent;
}
public static void main(String[] args) {
App a = new App('a', true);
App b = new App('A', false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
a.setOpponent(b);
b.setOpponent(a);
t1.start();
t2.start();
}
@Override
public synchronized void run() {
for (char i = 0; i < 26; i++) {
try {
if (go) {
System.out.println(c++);
this.wait();
synchronized (opponent) {
opponent.notify();
}
} else {
System.out.println(c++);
synchronized (opponent) {
opponent.notify();
}
this.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Here is the more simplified and understandable version of above code. In this code, both the threads are acquiring lock on same lock
object, printing the letter, going in WAIT
state (so release the lock) and giving turn to other BLOCKED
thread waiting for the lock on lock
. The other thread then acquires the lock on lock
, prints the letter, notifies the previous thread which was waiting on lock
object, and goes into WAIT
condition, thus releasing the lock.
public class App2 implements Runnable {
char c;
Object lock;
boolean go;
public App2(char c, boolean go) {
this.c = c;
this.go = go;
}
public void setLock(Object lock) {
this.lock = lock;
}
public static void main(String[] args) {
App2 a = new App2('a', true);
App2 b = new App2('A', false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
Object lock = new Object();
a.setLock(lock);
b.setLock(lock);
t1.start();
t2.start();
}
@Override
public void run() {
for (char i = 0; i < 26; i++) {
synchronized (lock) {
try {
if (go) {
System.out.println(c++);
lock.wait();
lock.notify();
} else {
System.out.println(c++);
lock.notify();
lock.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
UPDATE:
Above two approaches will work in most of the cases, but will not guarantee that t1
will be executed before t2
every time. Though t1
is started before t2
, but it does not mean that thread scheduler will always pick t1
to execute before t2
. To be sure that t1
will execute before t2
in every case, we need to ensure that t2
gets started once t1
is in RUNNABLE
state (i.e. running). Below is the one of the way of how we can achieve it:
public class App3 implements Runnable {
char c;
App3 opponent;
boolean go;
boolean createOpponent = false;
public App3(char c, boolean go) {
this.c = c;
this.go = go;
}
public void setOpponent(App3 opponent) {
this.opponent = opponent;
}
public void setCreateOpponent(boolean createOpponent) {
this.createOpponent = createOpponent;
}
public static void main(String[] args) {
App3 a = new App3('a', true);
App3 b = new App3('A', false);
Thread t1 = new Thread(a);
a.setOpponent(b);
a.setCreateOpponent(true);
b.setOpponent(a);
t1.start();
}
@Override
public synchronized void run() {
if (createOpponent) {
setCreateOpponent(false);
new Thread(opponent).start();
}
for (char i = 0; i < 26; i++) {
try {
if (go) {
System.out.println(c++);
this.wait();
synchronized (opponent) {
opponent.notify();
}
} else {
System.out.println(c++);
synchronized (opponent) {
opponent.notify();
}
this.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}