/* */ // Bank.java - A demo of synchronized threads. // This program starts two threads, one that transfer $100 from // checking to savings and back again. The other transfers $10 // back and forth. If the "synchronized" keyword is commented // out from transfer(), the balances will eventually be wrong. // // (C)2000 by Wayne Pollock, Tampa Florida USA. All Rights Reserved. import java.applet.*; import java.awt.*; import java.awt.event.*; public class Bank extends Applet implements Runnable, ActionListener { private Account checking, savings; private volatile boolean demoRunning; private Button btn; private TextField checkingTF, savingsTF; private Panel errorP; private static final int totalBal = 1000; private volatile Thread t1, t2; private static final int t1TransferAmount = 100; private static final int t2TransferAmount = 10; public void init () { checking = new Account( totalBal / 2 ); savings = new Account( totalBal - checking.bal ); demoRunning = false; checkingTF = new TextField( 7 ); checkingTF.setEditable( false ); checkingTF.setText( " $" + checking.bal ); checkingTF.setFont( new Font("Monospaced", Font.PLAIN, 14) ); savingsTF = new TextField( 7 ); savingsTF.setEditable( false ); savingsTF.setText( " $" + savings.bal ); savingsTF.setFont( new Font("Monospaced", Font.PLAIN, 14) ); btn = new Button( "Start" ); // This is also the stop button. btn.setBackground( Color.green.brighter() ); Label warning = new Label( "Balance Error!", Label.CENTER ); warning.setFont( new Font( "SansSerif", Font.BOLD, 24 ) ); warning.setForeground( Color.red ); Label title = new Label( "synchronized Thread Demo", Label.CENTER ); title.setFont( new Font( "SansSerif", Font.BOLD, 24 ) ); Label footer = new Label( "\u00A9 2000 by Wayne Pollock, " + "Tampa FL USA. All Rights Reserved.", Label.CENTER ); footer.setFont( new Font( "SansSeriff", Font.PLAIN, 10 ) ); // Layout the components: setLayout( new BorderLayout() ); setBackground( Color.lightGray ); Panel wrapper; // Used so buttons and things won't stretch. add( title, "North" ); Panel checkingP = new Panel(); checkingP.setLayout( new BorderLayout() ); checkingP.add( new Label( "Checking Bal", Label.CENTER ), "North" ); checkingP.add( checkingTF, "South" ); wrapper = new Panel(); wrapper.add( checkingP ); add( wrapper, "West" ); Panel savingsP = new Panel(); savingsP.setLayout( new BorderLayout() ); savingsP.add( new Label( "Savings Bal", Label.CENTER ), "North" ); savingsP.add( savingsTF, "South" ); wrapper = new Panel(); wrapper.add( savingsP ); add( wrapper, "East" ); errorP = new Panel(); errorP.setLayout( new BorderLayout() ); errorP.setVisible( false ); errorP.add( new Label( " " ), "North" ); // A Spacer. errorP.add( warning, "Center" ); Button resetBtn = new Button( " Reset " ); resetBtn.setBackground( Color.orange ); wrapper = new Panel(); wrapper.add( resetBtn ); errorP.add( wrapper, "South" ); add( errorP, "Center" ); Panel bot = new Panel(); bot.setLayout( new BorderLayout() ); wrapper = new Panel(); wrapper.add( btn ); bot.add( wrapper, "North" ); bot.add( new Label( " " ), "Center" ); // A spacer. bot.add( footer, "South" ); add( bot, "South" ); errorP.requestFocus(); // Hide the focus. // Hook up event listeners: btn.addActionListener( this ); resetBtn.addActionListener( new ActionListener () { public void actionPerformed ( ActionEvent e ) { reset(); } } ); } public void start () { // Create threads: t1 = new Thread( this, "thread1" ); t2 = new Thread( this, "thread2" ); t1.start(); t2.start(); } public void stop () { t1 = t2 = null; // The threads will check this and die. } private void reset () { errorP.setVisible( false ); checking.bal = totalBal / 2; savings.bal = totalBal - checking.bal; checkingTF.setText( " $" + checking.bal ); savingsTF.setText( " $" + savings.bal ); btn.setLabel( "Start" ); btn.setBackground( Color.green.brighter() ); btn.setEnabled( true ); errorP.requestFocus(); // Hide the focus. demoRunning = false; start(); } public synchronized void actionPerformed ( ActionEvent e ) { if ( demoRunning ) // then turn it off: { btn.setLabel( "Start" ); btn.setBackground( Color.green.brighter() ); } else { btn.setLabel( "Stop " ); btn.setBackground( Color.red ); } // Threads will check this flag and suspend themselves: demoRunning = ! demoRunning; notifyAll(); // Wake up sleeping threads. errorP.requestFocus(); // Hide the focus. } public void run () { Thread me = Thread.currentThread(); int amount; boolean toChecking = true; // Which way to transfer funds. if ( me == t1 ) amount = t1TransferAmount; else amount = t2TransferAmount; while ( me == t1 || me == t2 ) // else time to terminate! { try { // Pause for 0.1 seconds up to 1.0 seconds: Thread.sleep( (int) ( Math.random() * 900 ) + 100 ); // Check if demo is suspended (or terminated) yet: synchronized( this ) { while ( !demoRunning && ( me == t1 || me == t2 ) ) wait(); } } catch ( InterruptedException ignored ) { } if ( me != t1 && me != t2 ) // Check for terminate again. break; if ( toChecking ) transfer( checking, savings, amount ); else transfer( savings, checking, amount ); toChecking = ! toChecking; synchronized( this ) { checkingTF.setText( " $" + checking.bal ); savingsTF.setText( " $" + savings.bal ); if ( checking.bal + savings.bal != totalBal ) { errorP.setVisible( true ); stop(); btn.setEnabled( false ); // Disable Start/Stop btn validate(); } } } } // Try the effect of commenting out "synchronized"! // (The sleep statement just increases the probability of error.) private /*synchronized*/ void transfer ( Account toAccount, Account fromAccount, int amount ) { int bal = fromAccount.bal; pause(); bal -= amount; fromAccount.bal = bal; bal = toAccount.bal; pause(); bal += amount; toAccount.bal = bal; } private void pause () { if ( Math.random() < 0.35 ) try { Thread.sleep( 10 ); } catch ( Exception e ) {} } private static class Account { public int bal; Account ( int initialBalance ) { bal = initialBalance; } } } // End of class Bank
Send comments and mail to the WebMaster. |