一、用线程同步模拟银行取款操作
介绍: 当每个线程都是独立的,而且异步进行,也就是是每一根线程都包含了运行时的方法和数据,不需要外部的资源和方法,那么久不需要关心其他线程的状态和行为,但如果有多个线程要共享数据和方法,此时久需要考虑其他线程的状态和行为,否则可能导致程序运行的错误
二、题目
1.题目要求
张三和他的老婆各自拥有一张一行卡,可以对同一个一行账户进行取款操作,请使用多线程完成模拟张三和他老婆同时取款的操作
2.不考虑线程同步进行操作演示
实现步骤
- 定义一个账户类Accont
- 定义一个取款的线程类
- 定义测试类,对两人取款进行模拟
代码如下
- 账户类
设置初始化余额为500,方便显示取款余额不足时结果
取款后余额改变
public class Account {
private int balance = 500; //初始化余额
public int getBalance(){ //获取当前余额
return balance;
}
public void withDraw(int money){ //取款后余额变化
balance-=money;
}
}
- 线程类
应当使用实现Runnable接口的方式创建线程,而不是继承Thread类, 因为两个人的操作共享一个数据
两个线程操作同一个账户对象
添加取款方法,在内部实现余额判断并输出相关提示
重写run方法,循环五次取款操作
public class MoneyThread implements Runnable{
Account account = new Account(); //两个线程的取款操作是在同一个账户上进行
public void makeWithdrawal(int money){
if (account.getBalance()>=money){ //判断余额是否足够
System.out.println(Thread.currentThread().getName()+"准备取款");
try {
Thread.sleep(1000); //1秒后实现取款
} catch (InterruptedException e) {
e.printStackTrace();
}
account.withDraw(money); //取款
System.out.println(Thread.currentThread().getName()+"完成取款");
}else{ //余额不足时
System.out.println("账户余额不足支付"+Thread.currentThread().getName()
+"取款,当前余额为:"+account.getBalance());
}
}
@Override
public void run() {
for (int i = 0; i < 5; i++) { //循环5次取款操作
makeWithdrawal(100);
if (account.getBalance()<=0){
System.out.println("账户余额不足");
}
}
}
}
- 测试类
创建Runnable对象,创建两个新线程,每个线程都执行run方法
public class Test {
public static void main(String[] args) {
MoneyThread getMoney = new MoneyThread(); //创建Runnable对象
Thread thread1 = new Thread(getMoney,"张三");
Thread thread2 = new Thread(getMoney,"张三老婆");
//执行取款操作
thread1.start();
thread2.start();
}
}
运行结果
虽然在线程类中对余额做出了判断,但是任然出现了错误的操作
原因就是在取款的方法中,先检查余额是否足够,如果足够就取款,在判断的事件之中,有可能另一个线程以及完成了一次取款,导致余额发生变化,但当前判断的余额任然认为余额足够,因为余额不能实时变化,所以发生了透支的情况
因此在开发中,为了避免这种情况,要使用线程同步
三、线程同步
什么是线程同步: 当两个或以上的线程需要访问同一个资源的时候,需要以某种顺序来确保资源在某一个时刻至多只能被一个线程使用的方法叫做线程同步
线程同步的方法 同步代码块和同步方法,都是使用synchronized关键字来实现
-
同步方法
使用synchronized修饰的方法控制对类成员变量的访问。每一个类对应一把锁,方法一旦执行就独占该锁,知道方法结束时释放,此后其他被阻塞的线程才能获得该锁,重新进入可执行状态语法格式:
访问修饰符 synchronized 返回类型 方法名 {}
如何用同步方法的方式解决上面问题
public synchronized void makeWithdrawal(int money){
//省略方法中其他一致内容
}
运行结果
再添加synchronized关键字后,makeWithdrawal()方法成为同步方法,当一个线程正在操作时,其他操作此方法的线程进入阻塞状态直至该线程结束操作
-
同步代码块
同步代码块的实现机制与同步方法一致,但是应为可以针对任意的代码块,且可以任意指定上锁的对象,所以灵活度较高语法格式:
synchronized ( syncObject ) {
//需要同步访问控制的代码
}
如何用同步代码块的方式解决上面问题
public void makeWithdrawal(int money) {
synchronized(account){
//重复代码
}
}
运行结果
四、死锁
在使用线程同步机制时,如果多个线程都处于等待二无法唤醒时,就构成了死锁(Deadlock),此时处于等待的多个线程占用系统资源,但无法运行,因此不会释放自身的资源
在编程时应当之一死锁的问题,避免死锁的有效方法就是:线程因为某个条件为满足二受阻,不能让其继续占有资源;如果有多个对象需要互斥访问,应确定线程获得锁的顺序,并保证整个程序以相反的顺序释放锁