1、根据题目要求,我们需要创建一个账户实体类(Account),并在里面创建一个实现存钱的方法和一个取钱的方法。
package com.cover.day8; /** * 定义账户类 * * @author TSH * */ 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; /** * 账户操作的女生用户线程实现类 * @author TSH * */ 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; /** * 账户操作的男生用户线程实现类 * @author TSH * */ 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; /** * 测试类 * @author TSH * */ 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关键来修饰方法中的某一个代码块,那么这时候需要我们自己手动指定一个对象来当做这个一把锁。
