// BankDemo1.java

// by Mark Weiss, Fall 2002
// revised by Kip Irvine, 9/11/03

// Uses multiple monitors, but account transfers can cause a deadlock
 
class Account
{
    public void deposit( int d )
    {
		System.out.println("Entering deposit for account #" + ID);
        synchronized( CRITICAL_SECTION_1 )
        {
        	System.out.println("Entering deposit's critical section");
            balance += d;
            CRITICAL_SECTION_1.notifyAll( );
        }
    }
    
    public void withdraw( int d )
    {
		System.out.println("Entering withdraw for account #" + ID);
        synchronized( CRITICAL_SECTION_1 )
        {
        	System.out.println("Entering withdraw's critical section");
            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( );
    private int mAcctNum;
}

class Bank
{
    public Bank( int numAccounts )
    {
        accts = new Account[ numAccounts ];
        for( int i = 0; i < accts.length; i++ )
            accts[ i ] = new Account( );
    }
    
    // This method is deadlock prone!
    public static void transfer( Account to, Account from, int d )
    {
        if( to == from )
            return;
            
        System.out.println("About to transfer funds from account "
             + from.getID() + " to " + to.getID() );
            
        synchronized( from )
        {
            System.out.println("...locking acct #" + from.getID( ) );
            try // slow things down; CPU is too fast 
            {
                Thread.sleep( 1 );
            }
            catch( InterruptedException e )
            {
            }
            synchronized( to )
            {
            	System.out.println("...locking acct #" + to.getID( ) );
        			System.out.println("...transferring " + d 
        				+ " dollars from account " 
        				+ from.getID( )
        				+ " to " 
        				+ to.getID( ) );
                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( );
        
    	// randomly choose different back accounts as the source and target
    	// of money transfers.
        for( int i = 0; i < 50; i++ )
        {
            theBank.transfer( theBank.getAccount( (int) ( Math.random( ) * size ) ),
            	theBank.getAccount( (int) ( Math.random( ) * size ) ), 1 );
        }        
    }
    
    private Bank theBank;
    
}
class BankDemo1
{
    public static void main( String [] args )
    {
        Thread threads[] = new Thread[ 10 ];
        int numberOfAccounts = 4;
        Bank bank = new Bank( numberOfAccounts );
        
        for( int i = 0; i < threads.length; i++ )
        {
            threads[ i ] = new TapeThread( bank );
            threads[ i ].setPriority( 1 + i % 10 );		// 1,2,3,4,5,6,7,8,9,10
            threads[ i ].start( );
        }
        
        for( int i = 0; i < threads.length; i++ )
        {
            try { 
            	threads[ i ].join( );	// main will wait for all threads
            }
            catch( InterruptedException e ) {
                System.out.println( "Interrupted " + i );
            }
            System.out.println( "Thread " + i + " is Finished" );
        }
        
    }
}
        
