Adverts
I felt it was time for an example of concurrency that was a little more amusing than the usual dry examples so I put together this little application that plays Ping Pong with itself. This shows the use of a latch or barrier to control the game start and the wait and notify mechanism to control the actual game. In reality you probably wouldn't code this game like this. While there is nothing wrong with the use of the latch this isn't the general use for latches. It would be better to have the two players waiting on one another alternating between being producers and consumers. It would be possible to use a CyclicBarrier of one to control flow as well. Anyway, the example as it stands works and shows clearly how wait and notify can be used to control threads. You could easily extend this application to have any number of players in a sort of tournament type game.
Notice that the latch count down is called inside a synchronized block. This is to ensure that wait is entered before the nofity is called in the main thread. Failure to do this could result in a missed notification. When a player misses the ball it calls notify all to ensure that all threads wake up and notice that the game is over. This is the area that would need most work if the game was extended to N players.
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
* An amusing example of wait and notify.
*/
public class PingPong {
private volatile int attack = -1;
private volatile boolean gameOver = false;
public PingPong() {
CountDownLatch startLatch = new CountDownLatch( 2 );
Player ping = new Player( startLatch );
Player pong = new Player( startLatch );
ping.setName( "ping" );
pong.setName( "pong" );
ping.start();
pong.start();
try {
startLatch.await();
} catch (InterruptedException e) {
return;
}
synchronized (this) {
notify();
}
}
/**
* @param args
*/
public static void main(String[] args) {
new PingPong();
}
private class Player extends Thread {
private CountDownLatch latch;
Random random = new Random();
public Player( CountDownLatch latch ) {
this.latch = latch;
}
public void run() {
//Make the threads wait after starting so that we can trigger the start.
//User a CountDownLatch to ensure that all the players are ready before
//issuing the start up notification. Being in a synchronized block stops the
//notification happening before we have had a chance to call wait.
synchronized (PingPong.this) {
try {
latch.countDown();
PingPong.this.wait();
} catch (InterruptedException e) {
return; //We want to shutdown so lets exit.
}
}
while( !gameOver ) {
//The defence must be higher than the attack to return the shot
//failing to return the shot results in this player losing the game.
int defence = random.nextInt( 400 );
System.out.print( getName() + " Attack: " + attack + " Defence: " + defence );
if( defence < attack ) {
System.out.print( " Result: Failed. Missed the ball.\n" );
gameOver = true;
synchronized (PingPong.this) {
//The game is over we need to tell everyone.
PingPong.this.notifyAll();
}
} else {
attack = random.nextInt( 100 );
System.out.print( " Result: Success. Attacking with " + attack + "\n" );
synchronized (PingPong.this) {
PingPong.this.notify();
try {
PingPong.this.wait();
} catch (InterruptedException e) {
return; //We want to shutdown so lets exit.
}
}
}
}
}
}
}
I couldn't leave the above example with just two players as it was just too addictive so I extended it so that any number of players can play. The idea here is that when a player attacks it notifies another play that they need to defend. If they fail to defend another player is chosen to try and defend. Eventually only one player is left and they announce that they have won. The final play knows they have won as the players variable is gradually reduced to one. When this program is run what often happens is one player attacks with 99 and takes out a number of defenders in one go. The return statement just after the notify when a player loses is necessary to stop it declaring that it won.
import java.util.Random;
import java.util.concurrent.CountDownLatch;
/**
* An amusing example of wait and notify.
*/
public class PingPongMulti {
private volatile int attack = -1;
private volatile int players = 5;
public PingPongMulti() {
CountDownLatch startLatch = new CountDownLatch( 5 );
Player pang = new Player( startLatch );
Player peng = new Player( startLatch );
Player ping = new Player( startLatch );
Player pong = new Player( startLatch );
Player pung = new Player( startLatch );
pang.setName( "pang" );
peng.setName( "peng" );
ping.setName( "ping" );
pong.setName( "pong" );
pung.setName( "pung" );
pang.start();
peng.start();
ping.start();
pong.start();
pung.start();
try {
startLatch.await();
} catch (InterruptedException e) {
return;
}
synchronized (this) {
notify();
}
}
/**
* @param args
*/
public static void main(String[] args) {
new PingPongMulti();
}
private class Player extends Thread {
private CountDownLatch latch;
Random random = new Random();
public Player( CountDownLatch latch ) {
this.latch = latch;
}
public void run() {
//Make the threads wait after starting so that we can trigger the start.
//User a CountDownLatch to ensure that all the players are ready before
//issuing the start up notification. Being in a synchronized block stops the
//notification happening before we have had a chance to call wait.
synchronized (PingPongMulti.this) {
try {
latch.countDown();
PingPongMulti.this.wait();
} catch (InterruptedException e) {
return; //We want to shutdown so lets exit.
}
}
boolean missed = false;
while( !missed && players > 1 ) {
//The defence must be higher than the attack to return the shot
//failing to return the shot results in this player losing the game.
int defence = random.nextInt( 200 );
System.out.print( getName() + " Attack: " + attack + " Defence: " + defence );
if( defence < attack ) {
System.out.print( " Result: Failed. Missed the ball.\n" );
missed = true;
players--;
synchronized (PingPongMulti.this) {
//Let someone else try and return this attack.
PingPongMulti.this.notify();
return;
}
} else {
attack = random.nextInt( 100 );
System.out.print( " Result: Success. Attacking with " + attack + "\n" );
synchronized (PingPongMulti.this) {
PingPongMulti.this.notify();
try {
PingPongMulti.this.wait();
} catch (InterruptedException e) {
return; //We want to shutdown so lets exit.
}
}
}
}
System.out.println( getName() + " won the game." );
}
}
}