在大多数实际运行的多线程应用程序中,二个或多个线程需要共享对同一对象的访问。如果二个线程访问同一个对象,并且每个线程抖调用同一个方法,以便修改该对象的状态,那将出现什么样的情况呢?
书中最常见的例子就是银行取款的例子。假如一个人在银行里开了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
仑波比 阅读(200)
评论(0) 编辑 收藏