多
线程编程是有趣的事情,当常常容易突然出现“
错误的情况”,这是由于系统的线程调度具有一定的随机性。即使是程序运行期间偶尔出现的问题,那也是由于我们的编程不当所引起的。当使用多个线程来访问同一个数据时,非常容易出现
线程安全问题。
          关于线程安全问题,有一个经典的问题:
银行取钱问题。银行取钱的基本流程可以分为如下几个步骤: 
          【1】用户输入账户,密码,系统判断用户的账户,密码是否匹配。
          【2】用户输入取款金额。
          【3】系统判断账户余额是否大于取款金额。
          【4】如果余额大于取款金额,取款成功,否则取款失败。
           这个流程看上去没有任何问题。但一旦将这个流程放在多线程并发的场景下,就有可能出现问题,但不是说一定。也许程序运行一百万次都没有出现问题,没有出现问题并不等于没有问题!这就是多线程访问同一数据的时候,数据
安全性问题,在Java中主要有
三种方法解决线程安全问题:
同步代码块,同步方法,同步锁。在这里简要介绍下同步代码块方法。
          同步代码块语法格式如下:
          synchronized(){
         ...
         //此处代码就是同步代码块
}
         在以上语法中synchronized后括号里的obj就是同步监视器,以上代码的含义就是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。显然模拟一个取款过程可建立三个类:用户账户,取款类,测试类
           用户类Account代码:
package com.
thread.test;
/**
 * 用户类
 * 
 * @author wwb
 * 
 */
public class Account
{	
	/**
	 * 账户 
	 */
	private String accountNo;
	/**
	 * 账户余额
	 */
	private double balance;
	public Account(){}
	//
构造器
	public Account(String accountNo , double balance)
	{
		this.accountNo = accountNo;
		this.balance = balance;
	}
	public void setAccountNo(String accountNo)
	{
		this.accountNo = accountNo;
	}
	public String getAccountNo()
	{
		 return this.accountNo;
	}
	public void setBalance(double balance)
	{
		 this.balance = balance;
	}
	public double getBalance()
	{
		 return this.balance;
	}
	//下面两个方法根据accountNo来计算Account的
hashCode和判断equals
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if (obj != null 
			&& obj.getClass() == Account.class)
		{
			Account target = (Account)obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
           由于同步监视器的目的就是:阻止两条线程对同一个共享资源进行并发访问。因此通常推荐使用可能被并发访问的共享资源充当同步监视器。对于上面的取钱模拟程序,我们应该考虑使用账户作为同步监视器。因此以下的模拟取款程序代码如下:
             取款类DrawThread代码:
package com.thread.test;
/**
 * 取款类
 * 
 * @author wwb
 * 
 */
public class DrawThread extends Thread {
	// 模拟用户账户
	private Account account;
	// 当前取钱线程所希望取的钱数
	private double drawAmount;
	public DrawThread(String name, Account account,
			double drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	// 当多条线程修改同一个共享数据时,将涉及到数据安全问题。
	public void run() {
		// 使用account作为同步监视器,任何线程进入下面同步代码块之前,
		// 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
		// 这种做法符合:加锁-->修改完成-->释放锁 逻辑
		synchronized (account) {
			// 账户余额大于取钱数目
			if (account.getBalance() >= drawAmount) {
				// 吐出钞票
				System.out.println(getName() + "取钱成功!吐出钞票:" + drawAmount);
				try {
					Thread.sleep(1);
				} catch (InterruptedException ex) {
					ex.printStackTrace();
				}
				// 修改余额
				account.setBalance(account.getBalance() - drawAmount);
				System.out.println("\t余额为: " + account.getBalance());
			} else {
				System.out.println(getName() + "取钱失败!余额不足!");
			}
		}
	}
}
                测试类TestDraw代码:
package com.thread.test;
/**
 * 测试类 * 
 * @author wwb
 * 
 */
public class TestDraw {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// 创建一个账户
		Account acct = new Account("1234567", 1000);
		// 模拟两个线程对同一个账户取钱
		new DrawThread("甲", acct, 800).start();
		new DrawThread("乙", acct, 800).start();
	}
}