在編寫一個類時,如果該類中的代碼可能運行於多線程環境下,那麼就要考慮同步的問題。在Java中內置了語言級的同步原語--synchronized,這也大大簡化了Java中多線程同步的使用。 我們首先編寫一個非常簡單的多線程的程序,是模擬銀行中的多個線程同時對同一個儲蓄賬戶進行存款、取款操作的。
在程序中我們使用了一個簡化版本的Account類,代表了一個銀行賬戶的信息。在主程序中我們首先生成了1000個線程,然後啟動它們,每一個線程都對John的賬戶進行存100元,然後馬上又取出100元。這樣,對於John的賬戶來說,最終賬戶的余額應該是還是1000元才對。然而運行的結果卻超出我們的想像,首先來看看我們的演示代碼:
class Account
{
String name; float amount;
public Account(String name, float amount)
{
this.name = name;
this.amount = amount;
}
public void deposit(float amt)
{
float tmp = amount;
tmp += amt;
try
{
Thread.sleep(100);
//模擬其它處理所需要的時間,比如刷新數據庫等
}
catch (InterruptedException e)
{
// ignore
}
amount = tmp;
}
public void withdraw(float amt)
{
float tmp = amount;
tmp -= amt;
try
{
Thread.sleep(100);
//模擬其它處理所需要的時間,比如刷新數據庫等
}
catch (InterruptedException e)
{
// ignore
}
amount = tmp;
}
public float getBalance()
{
return amount;
}
}
public class AccountTest
{
private static int NUM_OF_THREAD = 1000;
static Thread[] threads = new Thread[NUM_OF_THREAD];
public static void main(String[] args)
{
final Account acc = new Account("John", 1000.0f);
for (int i = 0; i< NUM_OF_THREAD; i++)
{
threads[i] = new Thread(new Runnable()
{
public void run()
{
acc.deposit(100.0f);
acc.withdraw(100.0f);
}
}
);
threads[i].start();
}
for (int i=0; i<NUM_OF_THREAD; i++)
{
try { threads[i].join();
//等待所有線程運行結束
}
catch (InterruptedException e)
{
// ignore
}
}
System.out.println("Finally, John's balance is:" + acc.getBalance()); }}
注意,上面在Account的deposit和withdraw方法中之所以要把對amount的運算使用一個臨時變量首先存儲,sleep一段時間,然後,再賦值給amount,是為了模擬真實運行時的情況。因為在真實系統中,賬戶信息肯定是存儲在持久媒介中,比如RDBMS中,此處的睡眠的時間相當於比較耗時的數據庫操作,最後把臨時變量tmp的值賦值給amount相當於把amount的改動寫入數據庫中。運行AccountTest,結果如下(每一次結果都會不同):
E:\java\exer\bin>java AccountTest
Finally, John's balance is:3900.0
E:\java\exer\bin>java AccountTest
Finally, John's balance is:4900.0
E:\java\exer\bin>java AccountTest
Finally, John's balance is:4700.0
E:\java\exer\bin>java AccountTest
Finally, John's balance is:3900.0
E:\java\exer\bin>java AccountTest
Finally, John's balance is:3900.0
E:\java\exer\bin>java AccountTest
Finally, John's balance is:5200.0
為什麼會出現這樣的問題?這就是多線程中的同步的問題。在我們的程序中,Account中的amount會同時被多個線程所訪問,這就是一個競爭資源,通常稱作競態條件。對於這樣的多個線程共享的資源我們必須進行同步,以避免一個線程的改動被另一個線程所覆蓋。在我們這個程序中,Account中的amount是一個競態條件,所以所有對amount的修改訪問都要進行同步,我們將deposit()和withdraw()方法進行同步,修改為:
public synchronized void deposit(float amt)
{
float tmp = amount;
tmp += amt;
try
{
Thread.sleep(1);
//模擬其它處理所需要的時間,比如刷新數據庫等
}
catch (InterruptedException e)
{
// ignore
}
amount = tmp;
}
public synchronized void withdraw(float amt)
{
float tmp = amount;
tmp -= amt;
try
{
Thread.sleep(1);
//模擬其它處理所需要的時間,比如刷新數據庫等
}
catch (InterruptedException e)
{ // ignore }
amount = tmp;
}
此時,再運行,我們就能夠得到正確的結果了。Account中的getBalance()也訪問了amount,為什麼不對getBalance()同步呢?因為getBalance()並不會修改amount的值,所以,同時多個線程對它訪問不會造成數據的混亂。