Threads: Synchronization
AAHC

Click on A to make all fonts on the page smaller.

Click on A to make all fonts on the page larger.

Click on HC to toggle high contrast mode. When you move your mouse over some bold words in high contrast mode, related words are automatically highlighted. Text is shown in black and white.


Race Conditions

In the last lesson we made our first producer/consumer design patterns program. In this lesson, we'll explore synchronization further, expanding on our earlier examples. First we'll edit our Soup program to facilitate a cleaner design.

As you may recall, in that last lesson we left you with a conflict:

Not to worry. We programmers can always find ways to fix a problem! Let's go over the initial text in red first:

Exception in thread "AWT-EventQueue-1" java.util.ConcurrentModificationException

Race conditions usually involve one or more processes accessing a shared resource (such as a file or variable), where multiple access is not controlled properly.

Here's how a race condition might look in real life. Suppose on a given day, a husband and wife both decide to empty the same bank account and, purely by chance, they empty the account at the same time. If the two withdraw from the bank at the exact same time, causing the methods to be called at the exact same time, both ATMs could confirm that the account has enough cash and dispense it. The two threads access the account database at the same time.

The race condition exists here because the actions of checking the account and changing the account balance are not atomic. An atomic routine is one that can't be interrupted during its execution. In our banking example, if the actions of checking the account and changing the account balance were atomic, it would be impossible for a second thread to check on the account, until the first thread had finished changing the account status.

To avoid race conditions, we synchronize the eat() and add() methods. Synchronization prevents race conditions by preventing a second method from running before the first method is complete.

Now let's go back to the error in our Soup example. Our red text informs us of the location of the error:

at prodcons.MyTableSetting.paint (MyTableSetting.java:37)


It looks like we we did everything right--we got the contents of the buffer and put them into a contents variable so we could go through the ArrayList to print them out. So what's the problem?

Because we are accessing the variable that represents our shared resource in the getContents() method of Soup, the method should be synchronized so it won't be initiated while it is being accessed by another method. If we access and copy our method at the very moment that it is changing, it could cause the ConcurrentModificationException. This is an example of a race condition.

And even if we synchronized the method, we would still have problems. Since most collections in Java (for example, arrays and ArrayLists) are sent by reference, even though we use a method to get the contents and put them into another variable, they still point to the same place in memory. So when we get the contents back to the Applet and then print them out, we continue to access the shared resource in a potentially dangerous way. This is a problem with ArrayLists. In order to address these issues, we'll copy the buffer and then pass it back.

Fixing a Race Condition

Let's make a few changes to make things a little cleaner. We'll continue working with the classes we created in java4_Lesson9. In the java4_Lesson9/src/prodcons folder, edit the Consumer class as shown in blue:

CODE TO TYPE: Consumer
package prodcons;

class Consumer extends Thread {
    private Soup soup;
    private MyTableSetting bowlView;

    public Consumer(MyTableSetting bowl, Soup s) {
        bowlView = bowl;
        soup = s;
    }

    public void run() {
        String c;
        for (int i = 0; i < 10; i++) {       // stop thread when we know there are no more coming
            c = soup.eat();
            System.out.println("Ate a letter: " + c);
            bowlView.recentlyAte(c);          // tell what alphabet character to put in the spoon
            bowlView.repaint();

            try {
                sleep((int)(Math.random() * 3000));  // have consumer sleep a little longer or sometimes we never see them!
            } catch (InterruptedException e) { }
        }
    }
} 

Save it.

A thread stops when its run() method has finished. So in our example, after 10 alphabet letters have been produced and then consumed, their respective threads will stop and as such, they are considered "dead."

In java4_Lesson9/src/prodcons, edit the Soup class as shown in blue:

CODE TO EDIT: Soup
package prodcons;

import java.util.*;

class Soup {
    private ArrayList <String> buffer = new ArrayList<String>();;
    private int capacity = 6;

    public synchronized String eat() {
        while(buffer.isEmpty()){
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        String toReturn = buffer.get((int)(Math.random() * buffer.size()));
        buffer.remove(toReturn);
        buffer.trimToSize();
        notifyAll();
        return(toReturn);
    }

    public synchronized void add(String c) {
        while (buffer.size() == capacity) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        buffer.add(c);
        notifyAll();
    }

    public synchronized Object [] getContents(){  // see ArrayList about ConcurrentModificationException.
        Object [] temp = buffer.toArray();        // check out the API for ArrayList to see this toArray() method
        return (temp);                            // Make a clean copy so contents do not change when getting and/or displaying it
    } 
}

Save it.

In java4_Lesson9/src/prodcons, edit the MyTableSetting class as shown in blue and red:

CODE TO EDIT: MyTableSetting
package prodcons;

import java.applet.Applet;
//import java.util.*;            // don't need anymore because we have array copy
import java.awt.*;

public class MyTableSetting extends Applet {   
    Soup s;
    Producer p1;                // we need as Instance Variables so we can access outside of the init() 
    Consumer c1;
    int bowlLength = 150;
    int bowlWidth = 220;
    int bowlX = 60;
    int bowlY = 10;
    String justAte;

    public void init(){
        setSize(400,200);
        s = new Soup();
        p1 = new Producer(this, s);                                   // don't declare here again or it is only local
        System.out.println("Producer is in state " + p1.getState());  // show the state of the thread at this point
        c1 = new Consumer(this, s);

        p1.start();          
        c1.start(); 
        System.out.println("Consumer is in state " + c1.getState());   // show the state of the thread at this point    
    }

    public void paint(Graphics g){
        int x;
        int y;

        g.setColor(Color.yellow);
        g.fillOval(bowlX,bowlY, bowlWidth, bowlLength);
        g.setColor(Color.cyan);
        g.fillOval(10,25, 40, 55);
        g.fillOval(25,80, 8, 75);
        g.setColor(Color.black);
        g.drawOval(10,25, 40, 55);
        g.drawOval(25,80, 8, 75);
        g.drawOval(bowlX,bowlY, bowlWidth, bowlLength); 
        Font standardFont = getFont();                        // tell what just ate in spoon
        Font bigFont = new Font("Helvetica", Font.BOLD, 20);
        g.setFont(bigFont);
        if (justAte != null) {
            g.drawString(justAte, 25, 55);
            justAte = null;
        }
        else {
            g.setFont(standardFont);
            g.drawString("waiting", 13, 55);
            g.setFont(bigFont);
        }
        Object [] contents = s.getContents();  // bring back a fresh array of Object
        for(Object each : contents){           // no longer tied in memory to buffer in Soup
            x = bowlX + bowlWidth / 4 + (int)(Math.random() * (bowlWidth / 2));
            y = bowlY + bowlLength / 4 + (int)(Math.random() * (bowlLength / 2));
            g.drawString((String)each, x, y);                            // show the alphabet piece being eaten
        }
        System.out.println("Producer is in state " + p1.getState());   // show state of Producer (remember that we put it to sleep)
        System.out.println("Consumer is in state " + c1.getState());
        if(c1.getState()==Thread.State.TIMED_WAITING){                 // note access to enumerated types for Thread States
            checkState();                                               // get last repaint() in so see TERMINATED
        }
    }

    public void recentlyAte(String morsel){
        justAte = morsel;
    }	

    public void checkState(){
        try{Thread.sleep(1000);
        } catch(InterruptedException e) { }                           // Even the Applet has a Thread.  This command puts this (Applet's) Thread to sleep
        repaint();
    }
}

Save and run it. Look over the output in both the Console and the Applet's bowl and follow the progression of the states. Comment out the printlns and run it again without those distractions. You'll see that some states of the Thread are accessible through their inner enumerated class Thread.State.


Another Race Condition

The Applet itself is the main thread and can produce race conditions. When you ran your code, the code sometimes indicated that the first alphabet piece was eaten before the piece was even added to the Soup:

This shouldn't happen though, because our program would throw a NullPointerException if it had tried to eat something from the buffer that wasn't there yet. So the letter must have been in the buffer in order to then have been taken out. Hmm.

Maybe the System.out.printlns of MyTableSetting were running first and gave us a race condition. The main thread (the application or the applet) always takes priority. Fortunately, the class Thread contains methods to handle such situations, including join(), wait(), sleep(), getPriority(), and setPriority(), to name just a few.

Additional Resources

There's lots of material available to help you to work through concurrency and threads. Here's are just a few of them:


Using Threads in a Game

Now let's have some fun. We'll put threads and concurrency together into a game that tests your typing skills. A group of letters will be presented. The object of the game is to type them fast enough to keep a virtual bomb from exploding. You may have five bombs (attempts to type) visible at any given time, but in this version, after three bombs explode, the game is over.

A running example is provided here for you. Play around with it before we write the code, then as you write the code, the classes and their methods will make sense.

Creating the Typing Game
Preview: The Classes

Our game program will include the following classes:

  • Bomb: extends Thread.
  • Producer: extends Thread and produces the letters for players to type.
  • Consumer (not a Thread since in this version we have only one typist on a single keyboard): consumes the letters presented.
  • World, which is the monitor.
  • TypeorDie: extends Applet and provides the user interface.
The Bomb

Each bomb has a life of its own and is its own thread. Each bomb is associated with the set of letters or word presented underneath it and each has its own fuse (the red line under the word) that displays the amount of time remaining.

As you type the code, comments in the code provide information about the reason for each method. Also, the formal parameter specifies (char c) for methods that have a char passed--this does not mean the value is c. It just means that c is the variable name for the character passed.

Our java4_Lesson10 project comes with an images folder for this example. Click here, and then open it in the Package Explorer. It should look like this:

In java4_Lesson10, create a new Bomb class as shown:

Type Bomb as shown in blue:

CODE TO TYPE: Bomb
package bomb;

import java.awt.*;
import java.applet.Applet;

public class Bomb extends Thread {
    String word;
    int x, y, ticker;
    int width = 62;
    int height = 65;
    Applet apl;
    boolean being_disarmed = false;
    boolean disarmed = false;
    boolean exploded = false;
    int amount_disarmed = 0;
    Image bomb;
	
    public Bomb(String word, int x, int y, Applet apl){
        super(word);
        this.word = word;
        this.x = x;
        this.y = y;
        this.ticker = word.length()*6;  // time to type is relative to length of word
        this.apl = apl;
        bomb = apl.getImage(this.apl.getDocumentBase(), "../images/bomb.png");
    }

    public void run(){
        while (ticker > 0)                                    // have sleep in while loop to show kaboom!!!!!
        {
            try {
                sleep(600);
            }
            catch (InterruptedException e) {}
            ticker--;
            if (disarmed)                                      //check if disarmed here
            {
                break;                                          // jump out of while - this bomb is done
            }
            //System.out.println(word+":"+ticker);
            apl.repaint();
        }
        exploded = true;                                    // KABOOM!!! in draw method of this class - 
    }                                                       // will draw in Graphics passed from Applet

    public int getX(){
        return x;                                            // The horizontal component of the bomb's location is returned
    }            
	
    public int getY(){
        return y;                                            // the vertical component of the bomb's location is returned
    }  
	
    public int getWidth(){
        return width;                                        // The width of the bomb is returned
    }  
	
    public int getHeight(){
        return height;                                       // The height of the bomb is returned
    } 

    public void draw(Graphics g){                           // The bomb will be drawn to the Graphics object passed in
        if (!exploded)
        {
            g.drawImage(bomb, x, y,  Color.WHITE, apl);       // not exploded so show bomb			
            g.setFont(new Font("Monospaced", Font.PLAIN, 12));  
            g.setColor(Color.RED);
            g.drawChars(word.toCharArray(), 0, amount_disarmed, x, y+60);  // letters user typed turn red
            g.setColor(Color.BLACK);
            if (amount_disarmed != word.length())              // letters not typed stay black	
            {
                g.drawChars(word.toCharArray(), amount_disarmed, word.length()-amount_disarmed, x+(amount_disarmed*7), y+60);	
            }
            if (being_disarmed)
            {
                //System.out.println(word+" is being_disarmed");  // commented out System.outs that helped debug code
                g.setColor(Color.BLUE);                           // word being "worked on" is circled in blue
                g.drawRoundRect(x-2, y+49, word.length()*9, 14, 10, 10);
            }
                                                              // draw fuse
            g.setColor(Color.RED);                           
            double bar = (double)ticker/(word.length()*5);     
            g.fillRect(x, y+64, (int)(word.length()*7*bar), 5);  // red bar underneath shows progress
        }
        else
        {
            g.setColor(Color.RED);                               // else - bomb explodes 
            g.setFont(new Font("Courier Bold", Font.PLAIN, 12));
            g.drawString("KABOOM!!!", x, y+30);
        }
    }

    public boolean startsWith(char c)  {                      // does the current word start with the value typed (passed in here)
        if (word.charAt(0) == c)
            return true;
        return false;
    }

    public boolean exploded(){  
        return exploded;                                       // The Bomb's exploded variable will be returned 
    } 

    public boolean hasPoint(int x, int y){                    // If the Bomb occupies the location passed in, a boolean will be returned true
        if ( (this.x <= x && x <= (this.x + this.width)) && (this.y <= y && y <= (this.y + this.height)) )
            return true;
        else
            return false;	
    }

    public void setdisarming(){   
        being_disarmed = true;                                 // The bomb will be set to being disarmed  
    }

    public void setarming(){  
        being_disarmed = false;                                // The bomb will be set to  not being disarmed
    }

    public boolean attemptDisarm(char c){  
        assert amount_disarmed < word.length();             // assert - another debugging tool
        if (word.charAt(amount_disarmed) == c)                 // If the Bomb has been totally disarmed i.e. the char passed in was the last char needed to diffuse the bomb, 
        {                                                      // then true is returned, otherwise false
            //System.out.println(c+" is a hit on "+word);
            amount_disarmed++;
                                                             //check if bomb is totally disarmed
            if (amount_disarmed == word.length())
            {
                //System.out.println(word+" is defused");
                disarmed = true;
                return true;
            }
            return false;
        }
        //System.out.println(c+" is a miss on "+word);
        return false;
    }
}

Save it.

Now our code contains the assert statement. The assert statement was added to Java version 1.4 as a debugging tool. We'll address debugging practices in depth later, but for now, check out Oracle's documentation pages about Programming with Assertions.

Producing Words

The Producer in our Bomb game program is also a Thread that produces the words to type.

In the java4_Lesson10 project, create a new Producer class as shown:

Now type Producer as shown in blue:

CODE TO TYPE: Producer
package bomb;

import java.applet.Applet;

public class Producer extends Thread {
    private World myWorld;
    private String bank = "qwertyuiopasdfghjklzxcvbnm";  // alphabet characters from standard keyboard
    private Applet apl;
    private int bombRate = 2000;                         // rate can be fast or slow
    
    public Producer(World myWorld, Applet apl) {
        this.myWorld = myWorld;
        this.apl = apl;
    }

    public void toggleBombRate(){                        // user can change speed with GUI button
       if (bombRate == 2000)
           bombRate = 4000;
       else
           bombRate = 2000;
    }
	
    public void run() {
        String str;
        while (true)
        {
            int length = (int)Math.ceil(Math.random() * 6 );             // random length
            char []str_arry = new char[length];
            for (int i = 0; i < length; i++)
            {
                str_arry[i] = bank.charAt((int)(Math.random() * bank.length() ));   // random placement in string
            }
            str = new String(str_arry);
            //str = bank[((int)(Math.random() * 10))];
            int x = ((int)(Math.random() * 500));                        // random location
            int y = ((int)(Math.random() * 335));
            Bomb b = new Bomb(str, x, y, apl);
            while (myWorld.overlaps(b) )
            {
                b = new Bomb(str, b.getX()+10, y, apl);                   // prevent bomb overlaps
            }
            myWorld.add(b);
            System.out.println("Added bomb " + str + " to the world at "+b.getX()+", "+b.getY() );
            apl.repaint();
            try {
                sleep(bombRate);                                         // put up new words at speed of rate
            }
            catch (InterruptedException e) { }
        }
    }
}

Save it.

Consuming Keys

In our example, users are the consumers. As users type, they consume the words that have been placed in the World. To allow our users to do this, we include a KeyListener class. But since we won't need all of the KeyListener methods, we'll also use a KeyAdapter.

Look at the Adapter Class java.awt.event.Adapter and the interface that it implements java.awt.event.KeyListener to find out which methods are available.

In the java4_Lesson10 project, create a new Consumer class as shown:

Type Consumer as shown in blue:

CODE TO TYPE: Consumer
package bomb;

import java.applet.Applet;
import java.awt.event.*;

public class Consumer extends KeyAdapter {
    private World myWorld;
    private Applet apl;
  	
    public Consumer(World myWorld, Applet apl) {
        this.myWorld = myWorld;
        this.apl = apl;
        apl.addKeyListener(this);
    }

    public void keyTyped(KeyEvent e) {
        myWorld.type(e.getKeyChar() );             
        apl.repaint();
    }
}

Save it.

Monitoring Words

The monitor in this example watches over all of the words presented, as well as the user's typing speed and accuracy. Each new word has a bomb associated with it. This monitor considers the consumption of the words in two ways:

  1. Only five words are presented at one time.
  2. If a word is not typed fast enough, its bomb explodes. If three bombs explode, the monitor stops paying attention to the user.

In the java4_Lesson10 project, create a new World class as shown:

Type World as shown in blue:

CODE TO TYPE: World
package bomb;

import java.awt.*;

public class World {
    private final int MAX_BOMBS = 5;
    private Bomb bombs[] = new Bomb[MAX_BOMBS];  // shared resource - 5 bombs shown at a time
    private int typeNext = -1;
    private int addNext = 0;
    private int num_bombs = 0;
    private boolean isFull = false;
    private boolean isEmpty = true;
    private boolean gameOver = false;

    public synchronized void type(char c) {
        while (isEmpty == true)                   // if no words/bombs in buffer, wait for some
        {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
                                                  // check if three bombs have already blown up
        int num_exploded = 0;
        for(int i = 0; i < MAX_BOMBS; i++)     // check bombs in current value of shared resource
        {		
            if (bombs[i] != null && bombs[i].exploded)
                num_exploded++;
        }
        if (num_exploded >= 3)
        {
            gameOver = true;
            return;
        }
                                                             // check if entered char matches a bomb and if its fully disarmed
        if (typeNext < 0 || (bombs[typeNext].exploded) )  // no current diffusing bomb or current bomb blew up
        {
            for(int i = 0;i<MAX_BOMBS; i++)
            {
                if (bombs[i] != null && !bombs[i].exploded && bombs[i].startsWith(c) )
                {
                     typeNext = i;
                     bombs[typeNext].setdisarming();
                     break;
                }
            }
        }
        if (typeNext > -1 && !bombs[typeNext].exploded && bombs[typeNext].attemptDisarm(c) )
        {
            bombs[typeNext] = null;
            num_bombs--;
            typeNext = -1;
            if (num_bombs == 0 ) 
            {
                isEmpty = true;
            }
            isFull=false;
            notifyAll();
        }
    }

    public synchronized boolean overlaps(Bomb b){  // cannot put bombs on top of each other
       for (int i = 0; i<MAX_BOMBS; i++)
       {
           if (bombs[i] != null && (bombs[i].hasPoint(b.getX(), b.getY()) || 
            bombs[i].hasPoint(b.getX()+b.getWidth(), b.getY()) || 
            bombs[i].hasPoint(b.getX(), b.getY()+b.getHeight()) || 
            bombs[i].hasPoint(b.getX()+b.getWidth(), b.getY()+b.getHeight()) ) )
           {
               return true;
           }
       }	
       return false;
    }

    public synchronized void clearBombs(){   // clear bombs to reset
        for (int i = 0; i<MAX_BOMBS; i++)
        {
            bombs[i] = null;	
        }
        typeNext = -1;
        addNext = 0;
        num_bombs = 0;
        isFull = false;
        isEmpty = true;
        gameOver = false;
        notifyAll();
    }

    public synchronized void add(Bomb b) {  // add a bomb
        while (isFull == true)              // cannot add if full
        {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        //check for empty bomb spot
        int i;
        for (i = 0; i<MAX_BOMBS; i++)
        {
            if (bombs[i] == null)           // find empty space in array
            {
                bombs[i] = b; 
                break;                      // once find space, get out of for loop
            }
        }
        assert bombs[i] == b;
        num_bombs++;
        bombs[i].start();                   // light the fuse baby!

        if (num_bombs == MAX_BOMBS) 
        {
            isFull = true;
        }
        isEmpty = false;
        notifyAll();
    }

    public void draw(Graphics g){           // draw all of the bombs--called from Applet
        for (int i=0; i<MAX_BOMBS; i++)
        {
            if (bombs[i] != null)
                bombs[i].draw(g);
        }
        if (gameOver)
        {
            g.setColor(Color.BLACK);
            g.setFont(new Font("Monospaced", Font.PLAIN, 23));
            g.drawString("GAME OVER", 10, 390);
        }
    }
}

Save it.

The User Interface

The graphical user interface for our example is relatively straightforward:

Double-buffering, specifically a Double-Buffer Applet, was used here to create the GUI.

In the java4_Lesson10 project, create a new TypeOrDie class as shown:

Type TypeorDie as shown in blue:

CODE TO TYPE: TypeOrDie
package bomb;

import java.awt.event.*;
import java.awt.*;
import java.applet.Applet;

public class TypeOrDie extends Applet implements ActionListener {
    World myWorld;
    Button start_btn;
    Button slow_fast_btn;
    boolean started = false;
    Producer p1;
    Consumer c1;
	
    public void init(){
        setSize(560,400);

        start_btn = new Button("Start");   // add the buttons and listeners
        slow_fast_btn = new Button("Slower");
        start_btn.addActionListener(this);
        slow_fast_btn.addActionListener(this);
        this.add(start_btn);
        this.add(slow_fast_btn);
        myWorld = new World();             // instantiate everyone
        p1 = new Producer(myWorld, this);
        c1 = new Consumer(myWorld, this);
    }
	
    public void paint(Graphics g) {
        Dimension dim = getSize();         // set up double buffer 
        Image offscreen = createImage(dim.width, dim.height);         
        Graphics bufferGraphics = offscreen.getGraphics();
        bufferGraphics.clearRect(0,0, dim.width, dim.height);
	
        myWorld.draw(bufferGraphics);

        g.drawImage(offscreen, 0, 0, this);
    }

    public void update (Graphics g){
        paint(g);
    }

    public void actionPerformed(ActionEvent e){
        if (e.getActionCommand() == "Slower")
        {
            p1.toggleBombRate();
            slow_fast_btn.setLabel("Faster");
        }
        else if(e.getActionCommand() == "Faster")
        {
            p1.toggleBombRate();
            slow_fast_btn.setLabel("Slower");
        }
        else
        {
            myWorld.clearBombs();
            if (!started)                   // start the word creation
            {
                p1.start();
                started = true;
                start_btn.setLabel("Again");
            }
        }
        this.requestFocus();
    }
}

Now you see this in the Package Explorer:

Save and run it. Alternate between running the code and reading what each object does so you understand the reasons behind the implementation.

We Love Threads

Threads are a challenge, but with practice they'll serve you well. Check out these implementations of threads:

Real Games

You'll find lots of real games powered by Java here.

Blackjack

Blackjack is one of the most popular casino games in the world. Given our knowledge of threads, and the card capabilities we acquired in the last course, we could create our own blackjack game that allows multiple players.

Now suppose you have two or three players spread across the internet and they all choose to be Hit at the same time. You could have a race condition, and you could corrupt the game by giving all of the players the same card! Having the game set up so that multiple players can each play on their own separate thread will allow your game to go off without a hitch. Successful java programming--it's all about the threads.

You're doing great so far, keep it up!