- 线程互斥
并发线程从账户取款示例
package myThread;
class Account {
double balance;
public Account(double money) {
balance = money;
System.out.println("Totle Money: " + balance);
}
}
public class AccountThread extends Thread {
Account account;
int delay;
public AccountThread(Account account, int delay) {
this.account = account;
this.delay = delay;
}
public void run() {
if (account.balance >= 100) {
try {
sleep(delay);
account.balance = account.balance - 100;
System.out.println("withdraw 100 successful!");
} catch (InterruptedException e) {
}
} else
System.out.println("withdraw failed!");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Account account = new Account(100);
AccountThread acountThread1 = new AccountThread(account, 1000);
AccountThread acountThread2 = new AccountThread(account, 0);
acountThread1.start();
acountThread2.start();
}
}
执行以上代码得到结果
- 该结果非常奇怪,因为尽管账面上只有100元,但是两个取钱线程都取得了100元钱,也就是总共得到了200元钱。出错的原因在哪里呢?
- 由于线程1在判断满足取钱的条件后,被线程2打断,还没有来得及修改余额。因此线程2也满足取钱的条件,并完成了取钱动作。从而使共享数据balance的完整性被破坏。
- 互斥对象
上面的问题,并不是新问题,其实在并发程序设计中已经被研究并得到了解决。我们首先回忆两个概念。在并发程序设计中,对多线程共享的资源或数据成为临界资源,而把每个线(进)程中访问临界资源的那一段代码段成为临界代码段。通过为临界代码段设置信号灯,就可以保证资源的完整性,从而安全地访问共享资源
为了实现这种机制,Java语言提供以下两方面的支持:
1 为每个对象设置了一个“互斥锁”标记。该标记保证在每一个时刻,只能有一个线程拥有该互斥锁,其它线程如果需要获得该互斥锁,必须等待当前拥有该锁的线程将其释放。该对象成为互斥对象。因此,java中的每一个对象都是互斥对象.
2 为了配合使用对象的互斥锁,Java语言提供了保留字synchronized.其基本用法如下:
synchronized(互斥对象){
//临界代码段
}
使用互斥对象的并发线程示例
package myThread;
class Account {
double balance;
public Account(double money) {
balance = money;
System.out.println("Totle Money: " + balance);
}
}
public class AccountThread extends Thread {
Account account;
int delay;
public AccountThread(Account account, int delay) {
this.account = account;
this.delay = delay;
}
public void run() {
synchronized (account) {
if (account.balance >= 100) {
try {
sleep(delay);
account.balance = account.balance - 100;
System.out.println("withdraw 100 successful!");
} catch (InterruptedException e) {
}
} else
System.out.println("withdraw failed!");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Account account = new Account(100);
AccountThread acountThread1 = new AccountThread(account, 1000);
AccountThread acountThread2 = new AccountThread(account, 0);
acountThread1.start();
acountThread2.start();
}
}
执行以上代码得到结果
- 线程同步
在前面我们研究了共享资源的访问问题。在实际应用中,多个线程之间不仅需要互斥机制来保证对共享数据的完整性,而且有时需要多个线程之间互相协作,按照某种既定的步骤来共同完成任务。一个典型的应用是称之为生产-消费者模型。该模型可抽象为如下图。其约束条件为:
1) 生产者负责产品,并将其保存到仓库中;
2) 消费者从仓库中取得产品。
3) 由于库房容量有限,因此只有当库房还有空间时,生产者才可以将产品加入库房;否则只能等待。
只有库房中存在满足数量的产品时,消费者才能取走产品,否则只能等待。
实际应用中的许多例子都可以归结为该模型。如在操作系统中的打印机调度问题,库房的管理问题等。为了研究该问题,我们仍然以前面的存款与取款问题作为例子,假设存在一个账户对象(仓库)及两个线程:存款线程(生产者)和取款线程(消费者),并对其进行如下的限制;
1 只有当账户上的余额balance=0时,存款线程才可以存进100元;否则只能等待;
2 只有当账户上的余额balance=100时,取款线程才可以取走100元;否则只能等待。
根据生产-消费者模型,应该得到一个运行实例,存款100、取款100、存款100...很明显使用前面的synchronized关键字已经无法满足该实例线程同步实例。所以提供两个方法一个是wait(),一个notify()。
wait()方法的语义是:当一个线程执行了该方法,则该线程进入阻塞状态,同时让出同步对象的互斥锁,并自动进入互斥对象的等待队列。
notify()方法的语义是: 当一个线程执行了该方法,则拥有该方法的互斥对象的等待队列中的第一个线程被唤醒,同时自动获得该互斥对象的互斥锁,并进入就绪状态等待调度。
利用这两个方法,我们对上面的程序修改如下:
wait(),notify()使用实例
package myThread;
class Account1 {
double balance;
public Account1() {
balance = 0;
System.out.println("Totle Money: " + balance);
}
public synchronized void withdraw(double money) {
if (balance==0)
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
balance=balance-money;
System.out.println("Totle:"+balance+" withdraw 100 success");
notify();
}
public synchronized void deposite(double money) {
if (balance!=0)
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
balance=balance+money;
System.out.println("Totle:"+balance+" deposite 100 success");
notify();
}
}
//取钱线程
class WithdrawThread extends Thread{
Account1 account1;
public WithdrawThread(Account1 account1) {
this.account1=account1;
}
@Override
public void run() {
for (int i=0;i<5;i++) {
account1.withdraw(100);
}
}
}
//存钱线程
class DepositeThread extends Thread{
Account1 account1;
public DepositeThread(Account1 account1) {
this.account1=account1;
}
@Override
public void run() {
for (int i=0;i<5;i++) {
account1.deposite(100);
}
}
}
//测试
public class TestProCon {
public static void main (String args[]) {
Account1 account1=new Account1();
WithdrawThread withdrawThread=new WithdrawThread(account1);
DepositeThread depositeThread=new DepositeThread(account1);
withdrawThread.start();
depositeThread.start();
}
}
执行以上代码