一、题目需求:
- 现在有一个账户,这个账户的信息主要包括了账户号,账户名,账户余额。
- 那么请实现一下多人对这同一个账户进行存取钱的过程(保证线程安全的情况下)。
二、需求分析
1、根据题目要求,我们需要创建一个账户实体类(Account),并在里面创建一个实现存钱的方法和一个取钱的方法。
package com.cover.day8;
public class Account {
private String acccountCode;// 账户号码
private String name;// 账户名
private int balance;// 账户余额
public String getAcccountCode() {
return acccountCode;
}
public void setAcccountCode(String acccountCode) {
this.acccountCode = acccountCode;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public Account() {
super();
// TODO Auto-generated constructor stub
}
public Account(String acccountCode, String name, int balance) {
super();
this.acccountCode = acccountCode;
this.name = name;
this.balance = balance;
}
// 存钱
public synchronized void pos(int money) {
String name = Thread.currentThread().getName();
balance += money;
System.out.println(name + "刚刚向账户" + getAcccountCode() + "中存入" + money + "元,当前余额为" + balance + "元");
// 钱存好了,马上释放this这个锁了,这里可以叫醒其他线程,让他们从等待池进入到锁池
// 这个锁一旦释放,那么就锁池中的线程就会进入到就绪状态,争夺执行权以及获取锁对象,然后再执行代码
this.notifyAll();
}
// 取钱
public synchronized void wit(int money) {
String name = Thread.currentThread().getName();
while (balance < money) {
try {
this.wait();// 等待存钱线程执行完以后执行,这样就避免了线程冲突,防止了死锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
balance -= money;
System.out.println(name + "刚刚从账户" + getAcccountCode() + "中消费" + money + "元,当前余额为" + balance + "元");
}
}
2、账户创建好后,那么我们就来创建用户线程调用相应的存取钱方法进行操作,为了好区分,我们可以定义一个女生用户进行取钱操作,定义一个男生用户来存钱。
package com.cover.day8;
public class GirlsThread extends Thread {
private Account account;
public GirlsThread(Account account,String name) {
super(name);
this.account = account;
}
@Override
public void run() {
while(true) {
//定义一个随机的钱的数量
int money = (int)(Math.random()*1000+1);
try {
//调用取钱的方法进行随机取钱
account.wit(money);
//取完钱后睡眠会,让其他线程运行
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
package com.cover.day8;
public class BoyThread extends Thread {
private Account account;
public BoyThread(Account account,String name) {
super(name);
this.account = account;
}
@Override
public void run() {
while (true) {
//生成随机钱的数量
int money = (int) (Math.random() * 1000 + 1);
//实现随机存钱
account.pos(money);
try {
//当存完钱后进去休眠,然会让其他线程进行操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3、这样,整体的需求就算是已经完成了,我们最后就写个测试类进行测试成功与否即可!
package com.cover.day8;
public class AccountTest {
public static void main(String[] args) {
//创建账户对象,并初始化属性值,账户号,账户名,账户余额(1000元)
Account account = new Account("6220033004552316","tom",1000);
//创建男生对象,用于实现存钱操作,操作人名为"tom"
BoyThread boy = new BoyThread(account, "tom");
//创建三个女生对象,用于实现取钱操作,操作人名分别为"lily1"、"lily2"、"lily3"
GirlsThread girl1 = new GirlsThread(account, "lily1");
GirlsThread girl2 = new GirlsThread(account, "lily2");
GirlsThread girl3 = new GirlsThread(account, "lily3");
//调用start方法启动相应的线程
boy.start();
girl1.start();
girl2.start();
girl3.start();
}
}
4、这样我们的小练习就写好了,来看一下测试结果吧,为了方便操作,练习当中用的是一个死循环,所以必须手动停止, 不然会一直运行下去!
5、总结:在这个练习当中,为了保证线程安全,使用了synchronized关键字,也就是线程安全中锁的用法。那么对于锁的定义是怎么理解的呢,以下可以简单的阐述一下。
在代码中,如果有多个线程,同时去访问一段相关的代码或者一个共享的数据,那么这时候就可能出现并发访问的问题。就如我们这个例子,这个账户就是属于共享的资源数据,多个用户去操作,就是属于并发访问,其中就会暗藏着线程安全问题。
那么此时,synchronized就可以发挥作用了。synchronized可以对代码块进行加锁,锁的是这个代码中的代码,加锁之后,这个代码块中的代码只能让当前线程来执行,其他线程是不允许进来执行的,除非当前线程把这个代码块给执行完了或者把锁给释放了,那么其他线程才有机会进来执行。
使用synchronized关键字修饰代码块之后,需要指定一个对象,来充当这把锁,然后把代码块中的代码给锁住。只有拿到这把锁的线程,才有权利去执行这个加锁的代码块。注意,这个时候,一个线程想去执行加锁的代码块,那么就需要俩个条件,第一个条件就是线程要抢夺到cpu时间片的执行权,第二个条件就是线程要拿到代码块上放置的这一把锁,这把锁就是允许进入到这个代码块的通行证。
需要使用哪一个对象来充当这把锁,放置到加锁的代码块上面,作为可以进入到代码块中执行代码的通行证?
可以使用java中的任何一个对象,来当做这把锁。但是如果想在多个线程执行代码的时候,达到线程同步的效果, 那么就需要让这多个线程先去争取synchronized关键字所指定的对象,而这个对象就是充当了这把锁。使用synchronized关键来修饰类中的【非静态方法】的时候,默认是使用this对象来当做这一把锁。
使用synchronized关键来修饰类中的【静态方法】的时候,默认是使用当前类的Class对象来当做这一把锁。
使用synchronized关键来修饰方法中的某一个代码块,那么这时候需要我们自己手动指定一个对象来当做这个一把锁。