// BankDemo2.java

// by Mark Weiss, Fall 2002

// Uses multiple monitors and obtains them in a uniform order. 
// This prevents deadlock.

// Each Account has a unique ID number.

class Account
{
    public void deposit( int d )
    {
        synchronized( CRITICAL_SECTION_1 )
        {
            balance += d;
            CRITICAL_SECTION_1.notifyAll( );
        }
    }
    
    public void withdraw( int d )
    {
        synchronized( CRITICAL_SECTION_1 )
        {
            while ( balance < d )
            {
                try
                {
                    CRITICAL_SECTION_1.wait( );
                }
                catch( InterruptedException e )
                {
                }
            }
            balance -= d;
        }
    }    
    
    public int getID( )
    {
        return ID;
    }
    
    private final int ID = count++;		// new
    private static int count = 0;		// new
    
    private int balance = 10000;  // start with $10,000
    private Object CRITICAL_SECTION_1 = new Object( );
}

class Bank
{
    public Bank( int numAccounts )
    {
        accts = new Account[ numAccounts ];
        for( int i = 0; i < accts.length; i++ )
            accts[ i ] = new Account( );
    }
    
    // not deadlock prone: obtain monitors in same order.
    // (first, the lowest account number, then the higher account number)
    
    public static void transfer( Account to, Account from, int d )
    {
        Account low = to;
        Account high = from;
        
        if( to.getID( ) > from.getID( ) )
        {
            Account tmp = low;
            low = high;
            high = tmp;
        }
        
        synchronized( low )
        {
            try // slow things down; CPU is too fast 
            {
                Thread.sleep( 1 );
            }
            catch( InterruptedException e )
            {
            }
            
            synchronized( high )
            {
                from.withdraw( d );
                to.deposit( d );
            }
        }
    }
    
    public int size( )
    {
        return accts.length;
    }
       
    public Account getAccount( int num )
    {
        return accts[ num ];
    }
    
    private final Account [] accts;
}

class TapeThread extends Thread
{
    public TapeThread( Bank b )
    {
        theBank = b;
    }
    
    public void run( )
    {
        int size = theBank.size( );
        
        for( int i = 0; i < 100; i++ )
        {
            theBank.transfer( theBank.getAccount( (int) ( Math.random( ) * size ) ),
                       theBank.getAccount( (int) ( Math.random( ) * size ) ), 1 );
        }        
    }
    
    private Bank theBank;
    
}

class BankDemo2
{
    public static void main( String [] args )
    {
        Thread threads[] = new Thread[ 10 ];
        Bank bank = new Bank( 4 );
        
        for( int i = 0; i < threads.length; i++ )
        {
            threads[ i ] = new TapeThread( bank );
            threads[ i ].setPriority( 1 + i % 10 );
            threads[ i ].start( );
        }
        
        for( int i = 0; i < threads.length; i++ )
        {
            try
            {
                threads[ i ].join( );
            }
            
            catch( InterruptedException e )
            {
                System.out.println( "Interrupted " + i );
            }
            
            System.out.println( "Finished " + i );
        }
    }
}
        
  
