引子
由于多线程共享同一资源(临界资源),使得多线程程序结果会有不确定性。
怎么解决不确定性呢?以下两种方式可以部分控制不确定性:
线程互斥
线程同步
在熟悉一下两个概念:
临界区:用synchronized标记的代码段
临界资源:被临界区竞争的访问的资源
线程互斥
锁机制
线程互斥是使用锁机制来实现的,来看看锁机制:
- 标记出访问共享资源的代码段(Java就是用synchronized来标记代码段的,synchronized是个关键字),指明这段代码将作为一个整体以原子方式来访问共享资源;
- 给被访问的资源关联上一把锁;
- 当标记的的代码段(临界区)访问共享资源(临界资源)前,首先必须获得对象关联的锁;获得锁后将锁锁闭(lock),并开始实施访问;在标记的代码段访问结束后,释放锁;然后别的代码段就可以访问这个资源了。
- 只有对象才有锁,基本数据类型没有锁。
- 没有使用synchronized标记的代码段,锁机制不起作用。
- 无论是synchronized正常结束还是异常退出,都会释放锁。
使用格式
Synchronized标记方式有两种:
• synchronized(obj)area ; //obj是临界资源【一个对象】,area是临界区【一段代码】。
• synchronized方法声明
//比如:publicsynchronized void function(){……}
//等价于publicvoid function(){synchronized(this){area}}(第一种表达方式)
谈到线程互斥问题有个很经典的案例就是银行存钱取钱问题,来实现一下:
/**
* 存钱取钱问题
* 分析:
* 账户类Account 取钱线程类Saver 存钱线程类Fetcher
* 临界资源->账户类的实例 临界区->存取钱动作
* @author jin
*
*/
public class TakeSavingMoney {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Account account=new Account("晋瑜", 5000000);
// 存入1000000
(new Saver(account, 1000000)).start();
// 取出30000
(new Fetcher(account, 30000)).start();
// 取出450000
(new Fetcher(account, 450000)).start();
// 存入50000
(new Saver(account, 50000)).start();
}
}
class Account{ // 账户类
private String name; // 账户名
private double money; // 账户余额
public Account(String name, double money) {
// TODO Auto-generated constructor stub
this.name=name;
this.money=money;
}
public String getName(){
return this.name;
}
public double getMoney(){
return this.money;
}
public void put(double money){ // 存钱
this.money+=money;
}
public void get(double money){ // 取钱
this.money-=money;
}
}
class Saver extends Thread{ //存钱类
private Account account; // 存钱人拥有一个账户
private double money; // 将要存钱的数
public Saver(Account account, double money) {
// TODO Auto-generated constructor stub
this.account=account;
this.money=money;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (account) { // account是临界资源
//临界区
System.out.println(account.getName()+"账户\n"+"现有:"+account.getMoney()+"元\n"+"存入:"+this.money+"元.");
account.put(this.money);
System.out.println("现有余额:"+account.getMoney());
}
}
}
class Fetcher extends Thread{ //取钱类
private Account account; // 取款人持有一个账户
private double money; // 将要取钱的数
public Fetcher(Account account, double money) {
// TODO Auto-generated constructor stub
this.account=account;
this.money=money;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (account) {
//临界区
System.out.println(account.getName()+"账户\n"+"现有:"+account.getMoney()+"元\n"+"取出:"+this.money+"元.");
account.get(this.money);
System.out.println("现有余额:"+account.getMoney());
}
}
}
运行结果:
晋瑜账户
现有:5000000.0元
存入:1000000.0元.
现有余额:6000000.0
晋瑜账户
现有:6000000.0元
存入:50000.0元.
现有余额:6050000.0
晋瑜账户
现有:6050000.0元
取出:450000.0元.
现有余额:5600000.0
晋瑜账户
现有:5600000.0元
取出:30000.0元.
现有余额:5570000.0