Figure 1   SWMRG.java


 /////////////////////////////////////////////////////////////////
 //
 // SWMRG.java
 //
 
 /**
  * A Single Writer Multiple Reader Guard (SWMRG) class. By calling the 
  * startRead, endRead, startWrite and endWrite methods of a SWMRG object, a 
  * group of threads can synchronize their access to data. A SWMRG can thus be 
  * used to ensure that no readers are reading while a writer writes data (so 
  * readers don't get corrupt data) while still allowing multiple readers to 
  * read at the same time (so long as no writers are writing). 
  * @author Jonathan Locke 
  */
 public class SWMRG
 {
     /**
      * The number of currently active readers
      */
     protected int activeReaders = 0;
 
     /**
      * The number of currently active writers
      */
     protected int activeWriters = 0;
 
     /**
      * The number of waiting readers
      */
     protected int waitingReaders = 0;
 
     /**
      * The number of waiting writers
      */
     protected int waitingWriters = 0;
 
     /**
      * The number of writes that have occurred since the last read
      */
     protected int consecutiveWrites = 0;
 
     /**
      * The number of reads that have occurred since the last write
      */
     protected int consecutiveReads = 0;
 
     /**
      * The number of writes that are allowed in a row (before giving readers a 
      * chance). This can be used to ensure that readers don't 'starve.' 
      */
     public int maxConsecutiveWrites = 4;
 
     /**
      * The number of reads that are allowed in a row (before giving writers a 
      * chance). This can be used to ensure that writers don't 'starve.' 
      */
     public int maxConsecutiveReads = 32;
 
     /**
      * Uses the variables above to determine if a reader should be allowed to 
      * access the shared data. 
      * @return True if a reader should be allowed right now 
      */
     protected boolean allowReader()
     {
         if (activeWriters == 0)
         {
             if (waitingWriters == 0)
             {
                 return true;
             }
             return consecutiveReads < maxConsecutiveReads;
         }
         return false;
     }
 
     /**
      * Uses the variables above to determine if a writer should be allowed to access
      * the shared data.
      * @return True if a writer should be allowed right now
      */
     protected boolean allowWriter()
     {
         if (activeReaders == 0 && activeWriters == 0)
         {
             if (waitingReaders == 0)
             {
                 return true;
             }
             return consecutiveWrites < maxConsecutiveWrites;
         }
         return false;
     }
 
     /**
      * Call this method before attempting to read shared data. On returning you 
      * are guaranteed that no writers will change the shared data until you call 
      * endRead. This method waits until a reader is allowed (until allowReader 
      * returns true). At this point, there will be no active writers. 
      */
     public synchronized void startRead()
     {
         waitingReaders++;
         while (!allowReader())
         {
             try { wait(); } catch (InterruptedException e) { }
         }
         waitingReaders--;
         activeReaders++;
         consecutiveWrites = 0;
         if (waitingWriters > 0)
         {
             consecutiveReads++;
         }
     }
 
     /**
      * Call this method after reading shared data. This method decrements the 
      * number of active readers and notifies any waiting readers/writers. 
      */
     public synchronized void endRead()
     {
         activeReaders--;
         notifyAll();
     }
 
     /**
      * Call this method before attempting to change shared data. On returning you 
      * are guaranteed that no readers will attempt to read the shared data until 
      * you call endWrite. This method waits until a writer is allowed (until 
      * allowWriter returns true). At this point, there will be no active readers. 
      */
     public synchronized void startWrite()
     {
         waitingWriters++;
         while (!allowWriter())
         {
             try { wait(); } catch (InterruptedException e) { }
         }
         waitingWriters--;
         activeWriters++;
         consecutiveReads = 0;
         if (waitingReaders > 0)
         {
             consecutiveWrites++;
         }
     }
 
     /**
      * Call this method after changing shared data. This method decrements the 
      * number of active writers and notifies any waiting readers/writers. 
      */
     public synchronized void endWrite()
     {
         activeWriters--;
         notifyAll();
     }
}

Figure 3    TestSWMRG.java


 /////////////////////////////////////////////////////////////////
 //
 // TestSWMRG.java
 
 import java.applet.*;
 import java.awt.*;
 import java.awt.event.*;
 import java.util.*;
 
 /**
  * Test class to exercise/demonstrate the SWMRG class
  * @author Jonathan Locke
  */
 public class TestSWMRG extends Applet implements Runnable, ItemListener
 {
     static final int readers = 5;              // Number of reader threads
     static final int writers = 2;              // Number of writer threads
     static final int both = readers + writers; // Total number of threads
     Thread thread[] = new Thread[both];        // Reader and writer threads
     ThreadStatus t[] = new ThreadStatus[both]; // Status widgets for threads
     SWMRG s = new SWMRG();                     // Our SWMRG object
     int n = 0;                                 // Our SWMRG synchronized data
     int readerSpeed = 500;                     // Speed of readers in ms.
     int writerSpeed = 1000;                    // Speed of writers in ms.
     int readerIdle = 500;                      // Idle of readers in ms.
     int writerIdle = 1000;                     // Idle of writers in ms.
     boolean randomize = false;                 // Should randomize delays
     Choice choiceReaderSpeed = null;           // Choice for reader delay
     Choice choiceWriterSpeed = null;           // Choice for writer delay
     Choice choiceReaderIdle = null;            // Choice for reader idle
     Choice choiceWriterIdle = null;            // Choice for writer idle
     Checkbox checkRandomize = null;            // Randomize delays
     static final String strWriter = "Writer-"; // Writer prefix
     static final String strReader = "Reader-"; // Reader prefix
     static Random rnd = new Random();
 
     static String[] delays = { "100", "200", "500", "1000", "2000", "3000" };
 
     /**
      * Create components and start threads
      */
     public void init()
     {
         GridBagLayout gb = new GridBagLayout();
         setLayout(gb);
         GridBagConstraints c = new GridBagConstraints();
         c.gridx = 0;
         c.gridwidth = 1;
         c.weightx = 1.0;
         c.anchor = c.WEST;
         for (int i = 0; i < both; i++)
         {
             boolean isWriter = (i < writers);
             String name = isWriter ? (strWriter + (i + 1)) : (strReader + (i -  
                           writers + 1));
             if (i == writers)
             {
                 add(new Label(""), c);
             }
             add(t[i] = new ThreadStatus(name + ": Starting up", isWriter), c);
             thread[i] = new Thread(this, name);
             thread[i].start();
         }
 
         add(new Label(""), c);
         add(new Label("Reader idle time (in milliseconds):"), c);
         add(choiceReaderIdle = new Choice(), c);
         add(new Label("Reading speed (in milliseconds):"), c);
         add(choiceReaderSpeed = new Choice(), c);
         add(new Label(""), c);
         add(new Label("Writer idle time (in milliseconds):"), c);
         add(choiceWriterIdle = new Choice(), c);
         add(new Label("Writing speed (in milliseconds):"), c);
         add(choiceWriterSpeed = new Choice(), c);
         add(new Label(""), c);
         add(checkRandomize = new Checkbox("Randomize delays"), c);
 
         for (int i = 0; i < delays.length; i++)
         {
             choiceReaderSpeed.add(delays[i]);
             choiceWriterSpeed.add(delays[i]);
             choiceReaderIdle.add(delays[i]);
             choiceWriterIdle.add(delays[i]);
         }
 
         choiceReaderIdle.select("500");
         choiceWriterIdle.select("500");
         choiceReaderSpeed.select("1000");
         choiceWriterSpeed.select("1000");
 
         choiceReaderSpeed.addItemListener(this);
         choiceWriterSpeed.addItemListener(this);
         choiceReaderIdle.addItemListener(this);
         choiceWriterIdle.addItemListener(this);
 
         updateValues();
     }
 
     /**
      * A choice changed, so we need to update the delay values
      * @param e Ignored
      */
     public void itemStateChanged(ItemEvent e)
     {
         updateValues();
     }
 
     /**
      * Return insets for applet
      * @return Applet insets
      */
     public Insets getInsets()
     {
         return new Insets(4, 10, 4, 10);
     }
     
     /**
      * Get delay choices
      */
     void updateValues()
     {
         readerSpeed = Integer.parseInt(choiceReaderSpeed.getSelectedItem());
         writerSpeed = Integer.parseInt(choiceWriterSpeed.getSelectedItem());
         readerIdle = Integer.parseInt(choiceReaderIdle.getSelectedItem());
         writerIdle = Integer.parseInt(choiceWriterIdle.getSelectedItem());
         randomize = checkRandomize.getState();
     }
 
     /**
      * Read or write according to the rules
      */
     public void run()
     {
         delay(3000);
         while (true)
         {
             if (Thread.currentThread().getName().startsWith(strWriter))
             {
                 write();
             }
             else
             {
                 read();
             }    
         }
     }
 
     /**
      * Gets ThreadStatus UI component for current thread
      */
     ThreadStatus getThreadStatus()
     {
         for (int i = 0; i < both; i++)
         {
             if (thread[i] == Thread.currentThread())
             {
                 return t[i];
             }
         }
         return null;
     }
 
     /**
      * Sets the state of the current thread
      * @param state New state
      */
     void setState(int state)
     {
         getThreadStatus().setState(state);
         switch (state)
         {
             case ThreadStatus.WAITING: say ("Waiting.");        break;
             case ThreadStatus.WRITING: say ("Writing.");        break;
             case ThreadStatus.READING: say ("Read " + n + "."); break;
             case ThreadStatus.IDLE:    say ("Idle.");           break;
         }
     }
 
     /**
      * Say something for the current thread
      */
     void say(String s)
     {
         getThreadStatus().say(Thread.currentThread().getName() + ": " + s);
     }
     /**
      * Wait for a bit
      * @param milliseconds How long to delay
      */
     void delay(long milliseconds)
     {
         if (randomize)
         {
             milliseconds += (rnd.nextInt() % (milliseconds / 2));
         }
         try { Thread.sleep(milliseconds); } catch (InterruptedException e) {}
     }
 
     /**
      * Perform a read operation
      */
     void read()  
     {
         setState(ThreadStatus.WAITING);
         delay(100);
 
         // Use SWMRG to wait until we can read
         s.startRead();
 
         setState(ThreadStatus.READING);
         delay(readerSpeed);
 
         // An odd number would indicate a bug in SWMRG.
         if ((n % 2) == 1)
         {
             say("ERROR! (n = " + n + ")");
             delay(100000);
         }
 
         // Done reading
         s.endRead();
         setState(ThreadStatus.IDLE);
         delay(readerIdle);
     }
 
     /**
      * Perform a write operation
      */
     void write() 
     {
         setState(ThreadStatus.WAITING);
         delay(100);
 
         // Use SWMRG to wait until we can write
         s.startWrite();
 
         setState(ThreadStatus.WRITING);
         n++; 
         say("Starting write (n=" + n + ")");
         delay(writerSpeed / 2);
         n++; 
         say("Finishing write (n=" + n + ")");
         delay(writerSpeed / 2);
 
         // Done writing
         s.endWrite();
         setState(ThreadStatus.IDLE);
         delay(writerIdle);
     }
 
     /**
      * Frame class to hold applet
      */
     class AppletFrame extends Frame
     {
         /**
          * Constructor
          * @param title Title of applet frame
          */
         public AppletFrame(String title)
         {
             super(title);
 
             // Add a window event listener (a nested-class adapter)
             // to listen for window close events.
             addWindowListener
             (
                 new WindowAdapter()
                 {
                     public void windowClosing(WindowEvent e)
                     {
                         System.exit(0);
                     }
                 }
             );
         }
     }
 
     /**
      * Constructor
      * @param arg Command-line arguments
      */
     public TestSWMRG()
     {
         AppletFrame f = new AppletFrame("TestSWMRG");
         f.setLayout(new FlowLayout());
         f.add(this);
         init();
         f.pack();
         f.show();       
     }
     
     /**
      * Main application entrypoint.
      * @param arg Command-line arguments
      */
     static public void main(String[] arg)
     {
         new TestSWMRG();
     }
 }


Figure 4    ThreadStatus.java


/////////////////////////////////////////////////////////////////
//
// ThreadStatus.java
//

import java.awt.*;

/**
 * A simple custom component to show the status of a thread graphically.
 * @author Jonathan Locke
 */
class ThreadStatus extends Canvas
{
    public static final int IDLE = 0;       // Thread is idle
    public static final int READING = 1;    // Thread is reading data
    public static final int WRITING = 2;    // Thread is writing data
    public static final int WAITING = 3;    // Thread is waiting

    protected String str;                   // Current text to display
    protected int state = IDLE;             // State of thread
    protected Image img = null;             // Offscreen buffer
    protected int dxImg = 0;                // Width of image
    protected int dyImg = 0;                // Height of image
    protected boolean isWriter = false;     // True if thread is a writer

    protected Color stateColor[] = 
    {
        new Color(96, 96, 0),               // IDLE color
        new Color(0, 255, 0),               // READING color
        new Color(255, 0, 0),               // WRITING color
        new Color(255, 255, 0)              // WAITING color
    };

    /**
     * Constructor
     * @param str String to display initially
     * @param isWriter True if the thread plans to write data
     */
    public ThreadStatus(String str, boolean isWriter)
    {
        say(str);
        this.isWriter = isWriter;
    }

    /**
     * Say something
     * @param str What to say for the thread (since they can't talk)
     */
    public void say(String str)
    {
        this.str = str;
        repaint();
    }

    /**
     * Set the state of the thread
     * @param state New state to draw
     */
    public void setState(int state)
    {
        this.state = state;   
        repaint();
    }

    /**
     * Draw component
     * @param g Graphics context to draw on
     */
    public void paint(Graphics g)
    {
        int dx = getSize().width;
        int dy = getSize().height;
        if (img == null || dx > dxImg || dy > dyImg)
        {
            img = createImage(dxImg = Math.max(dx, dxImg), dyImg = Math.max(dy, 
                  dyImg));
        }
        Graphics gm = img.getGraphics();
        gm.setFont(getFont());
        gm.setColor(Color.black);
        gm.fillRect(0, 0, dx, dy);
        Rectangle r = new Rectangle(4, 4, dy - 8, dy - 8);
        gm.setColor(stateColor[state]);
        gm.fillOval(r.x, r.y, r.width, r.height);
        gm.setColor(isWriter ? Color.red : Color.green);
        FontMetrics f = gm.getFontMetrics();
        gm.drawString(str, r.x + r.width + r.width, dy - ((dy - f.getAscent()) / 2) - 
                      f.getDescent());
        g.drawImage(img, 0, 0, null);
    }

    /**
     * Avoid flicker
     * @param g Graphics context
     */
    public void update(Graphics g)
    {
        paint(g);
    }

    /**
     * Return preferred size of ThreadStatus component
     * @return Preferred size
     */
    public Dimension getPreferredSize()
    {
        Graphics g = getGraphics();
        FontMetrics fm = g.getFontMetrics(getFont());
        return new Dimension(fm.charWidth('X') * 40, fm.getHeight() + 4);
    }
}