在大多數實際運行的多線程應用程序中,二個或多個線程需要共享對同一對象的訪問。如果二個線程訪問同一個對象,并且每個線程抖調用同一個方法,以便修改該對象的狀態,那將出現什么樣的情況呢?
書中最常見的例子就是銀行取款的例子。假如一個人在銀行里開了10個帳戶,外面設置10個線程,每個帳戶一個線程。每個交易事務負責將一筆隨機數額的資金從該線程服務的帳戶轉移到另一個隨機帳戶。
在沒有對共享資源的訪問實施同步(synchronized)之前,在同時進行轉賬時,就會出現錯誤。例如下面一代代碼:
public class UnsynchBankTest


{
public static void main(String[] args)

{
Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE);
int i;
for (i = 0; i < NACCOUNTS; i++)

{
TransferThread t = new TransferThread(b, i,
INITIAL_BALANCE);
t.setPriority(Thread.NORM_PRIORITY + i % 2);
t.start();
}
}

public static final int NACCOUNTS = 10;
public static final int INITIAL_BALANCE = 10000;
}


/** *//** *//** *//**
A bank with a number of bank accounts.
*/
class Bank


{

/** *//** *//** *//**
Constructs the bank.
@param n the number of accounts
@param initialBalance the initial balance
for each account
*/
public Bank(int n, int initialBalance)

{
accounts = new int[n];
int i;
for (i = 0; i < accounts.length; i++)
accounts[i] = initialBalance;
ntransacts = 0;
}


/** *//** *//** *//**
Transfers money from one account to another.
@param from the account to transfer from
@param to the account to transfer to
@param amount the amount to transfer
*/
public void transfer(int from, int to, int amount)
throws InterruptedException

{
accounts[from] -= amount;
accounts[to] += amount;
ntransacts++;
if (ntransacts % NTEST == 0) test();
}


/** *//** *//** *//**
Prints a test message to check the integrity
of this bank object.
*/
public void test()

{
int sum = 0;

for (int i = 0; i < accounts.length; i++)
sum += accounts[i];

System.out.println("Transactions:" + ntransacts
+ " Sum: " + sum);
}


/** *//** *//** *//**
Gets the number of accounts in the bank.
@return the number of accounts
*/
public int size()

{
return accounts.length;
}

public static final int NTEST = 10000;
private final int[] accounts;
private long ntransacts = 0;
}


/** *//** *//** *//**
A thread that transfers money from an account to other
accounts in a bank.
*/
class TransferThread extends Thread


{

/** *//** *//** *//**
Constructs a transfer thread.
@param b the bank between whose account money is transferred
@param from the account to transfer money from
@param max the maximum amount of money in each transfer
*/
public TransferThread(Bank b, int from, int max)

{
bank = b;
fromAccount = from;
maxAmount = max;
}

public void run()

{
try

{
while (!interrupted())

{
for (int i = 0; i < REPS; i++)

{
int toAccount = (int)(bank.size() * Math.random());
int amount = (int)(maxAmount * Math.random() / REPS);
bank.transfer(fromAccount, toAccount, amount);
sleep(1);
}
}
}

catch(InterruptedException e)
{}
}

private Bank bank;
private int fromAccount;
private int maxAmount;
運行一段時間后會發現,sum(總金額發生了變化)。
下面這段代碼是對共享資源的訪問實施同步:
public class SynchBankTest


{
public static void main(String[] args)

{
Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE);
int i;
for (i = 0; i < NACCOUNTS; i++)

{
TransferThread t = new TransferThread(b, i,
INITIAL_BALANCE);
t.setPriority(Thread.NORM_PRIORITY + i % 2);
t.start();
}
}

public static final int NACCOUNTS = 10;
public static final int INITIAL_BALANCE = 10000;
}


/** *//**
A bank with a number of bank accounts.
*/
class Bank


{

/** *//**
Constructs the bank.
@param n the number of accounts
@param initialBalance the initial balance
for each account
*/
public Bank(int n, int initialBalance)

{
accounts = new int[n];
int i;
for (i = 0; i < accounts.length; i++)
accounts[i] = initialBalance;
ntransacts = 0;
}


/** *//**
Transfers money from one account to another.
@param from the account to transfer from
@param to the account to transfer to
@param amount the amount to transfer
*/
public synchronized void transfer(int from, int to, int amount)
throws InterruptedException

{
while (accounts[from] < amount)
wait();
accounts[from] -= amount;
accounts[to] += amount;
ntransacts++;
notifyAll();
if (ntransacts % NTEST == 0) test();
}


/** *//**
Prints a test message to check the integrity
of this bank object.
*/
public synchronized void test()

{
int sum = 0;

for (int i = 0; i < accounts.length; i++)
sum += accounts[i];

System.out.println("Transactions:" + ntransacts
+ " Sum: " + sum);
}


/** *//**
Gets the number of accounts in the bank.
@return the number of accounts
*/
public int size()

{
return accounts.length;
}

public static final int NTEST = 10000;
private final int[] accounts;
private long ntransacts = 0;
}


/** *//**
A thread that transfers money from an account to other
accounts in a bank.
*/
class TransferThread extends Thread


{

/** *//**
Constructs a transfer thread.
@param b the bank between whose account money is transferred
@param from the account to transfer money from
@param max the maximum amount of money in each transfer
*/
public TransferThread(Bank b, int from, int max)

{
bank = b;
fromAccount = from;
maxAmount = max;
}

public void run()

{
try

{
while (!interrupted())

{
int toAccount = (int)(bank.size() * Math.random());
int amount = (int)(maxAmount * Math.random());
bank.transfer(fromAccount, toAccount, amount);
sleep(1);
}
}

catch(InterruptedException e)
{}
}

private Bank bank;
private int fromAccount;
private int maxAmount;
}
運行后,sum未發生變化。
簡要說明一下同步機制是如何運行的:
1.若要調用synchronized方法,隱含參數不應該被鎖定。調用該方法便可鎖定該對象。而從該調用返回則可撤銷對隱含參數對象的鎖定。因此,每次只有一個線程能夠在特定對象上執行synchronized方法。
2.當一個線程執行對wait方法的調用時,他將釋放對象鎖,而且進入該對象的等待列表。
3.要從等待列表中刪除一個線程,另外的莫個線程必須調用同一對象上的notifyALL或notify方法。
調度原則確實是復雜的,不過使用起來是相對簡單的。你只要按照下面的5條原則進行操作即可:
1.如果二個或多個線程修改一個對象,請將執行修改的方法聲明為synchronized方法。受到對象修改影響的只讀方法也必須實現同步。
2.如果一個線程必須等待某個對象的狀態出項變更,那么它應該在對象的內部等待,而不是在外邊等待,這可以通過輸入一個synchronized方法,并調用wait方法實現。
3.不要在synchronized方法中花費大量的時間。大多數操作只是更新數據結構,然后很快返回。如果你不能立即完成synchronized方法的操作,那么請調用wait方法,這樣你就可以在等待時釋放該對象鎖。
4.每當一個方法改變某個對象的狀態時,太就應該調用notifyALL方法。這樣可以給等待的線程一個機會,以便查看環境有沒有發生變化。
5.請記住,wait和notifyALL/notify方法都屬于Object類的方法,而不是Thread類的方法。請反復檢查你對wait方法的調用同一對象上的通知是否匹配。
posted on 2008-10-19 21:23
侖波比 閱讀(202)
評論(0) 編輯 收藏