java线程学习_02

线程同步

线程安全问题(银行取钱案例)

银行取钱的基本流程基本上可以分为以下步骤

  1. 用户输入账户,密码,判断用户账户,密码是否匹配(此处省略这一步)
  2. 用户输入取款金额
  3. 系统判断账户余额是否大于取款金额
  4. 如果余额大于存款金额,则取款成功;如果余额小于存款金额,则取款失败
package thread;

/**
 * @author Mike
 * @use 账户类
 */
public class Account {
    private String accountNo;
    private double balance;

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account(){}
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }
    public int hashCode(){
        return accountNo.hashCode();
    }
    public boolean equals(Object object){
        if(this == object){
            return true;
        }
        if (object!=null && object.getClass() == Account.class){
            Account target = (Account)object;
            return target.getAccountNo().equals(accountNo);
        }
        return false;
    }
}

package thread;

/**
 * @author Mike
 * @use 提供一个取钱的线程类,该线程类根据执行账户、取钱数量进行取钱操作
 * ,取钱的逻辑是当余额不足时,提示余额不足,当余额大于取的钱数时,余额减少
 */
public class DrawThread extends Thread{
    private Account account;
    private double drawAmount;
    public DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    //当多个线程修改同一个共享数据时,将涉及数据安全问题
    public void run(){
        //synchronized (account){
            if (account.getBalance() >= drawAmount){
                System.out.println(getName()+"取钱成功,取出"+drawAmount);
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("\t余额为:"+account.getBalance());
            }else {
                System.out.println(getName() + "取钱失败!余额不足!");
            }
				//注释内容为同步代码块
      //  }
    }
}

测试类:

package thread;

/**
 * @author Mike
 * @use 银行取钱测试类
 */
public class DrawTest {
    public static void main(String[] args) {
        Account account = new Account("1234567" , 1000);
        new DrawThread("甲" ,account ,800).start();
        new DrawThread("乙",account,600).start();
    }
}

输出结果
加入两个人同时从这个账户里取钱,可能会出现余额小于0的情况(可以这样理解:甲取出钱,在余额还没有来得及更新的时候,乙也来取钱了),显然这样的情况我们不想让它发生,所以我们用同步代码块来解决共享数据的安全性问题

同步代码块

synchronized(obj){
...
//此处的代码就是同步代码块
}

对Account类进行修改

把18行注释打开就好了,当然别忘了打开尾括号的注释

同步锁lock()

比较常用的是ReentrantLock(可重入锁)(反正书上说这个常用,我还没做过关于线程的项目,也不清楚)
使用时的代码格式如下

class X{
	//定义锁对象
	private final ReentrantLock lock = new ReentrantLock();
	//定义需要保证线程安全的方法
	public void m(){
	//加锁
	lock.lock();
	try{
	//需要保证线程安全的代码
		}
		//使用finally块来保证释放锁
		finally{
		lock.unlock();
		}
	}
}

下面是银行问题用lock改写的代码
Account类

package thread._16_6;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Mike
 * @use 使用ReentrantLock重新完成锁内容
 */
public class Account {
    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock();
    private String accountNo;
    private double balance;
    public Account(){}
    public Account(String accountNo,double balance){
        this.accountNo=accountNo;
        this.balance=balance;
    }
    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }
    public double getBalance() {
        return this.balance;
    }
    //提供一个线程安全的draw()方法来完成取钱操作
    public void draw(double drawAmount){
        //加锁
        lock.lock();
        try{
            //账户余额大于取钱数目
            if (balance>=drawAmount){
                System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                balance -=drawAmount;
                System.out.println("余额为:"+balance);
            }else{
                System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足");
            }

        }finally {
            //修改完成,释放锁
            lock.unlock();
        }
    }
}

线程类

package thread._16_6;


/**
 * @author Mike
 * @use 提供一个取钱的线程类,该线程类根据执行账户、取钱数量进行取钱操作
 * ,取钱的逻辑是当余额不足时,提示余额不足,当余额大于取的钱数时,余额减少
 */
public class DrawThread extends Thread{
    private Account account;
    private double drawAmount;
    public DrawThread(String name, Account account, double drawAmount){
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }
    //当多个线程修改同一个共享数据时,将涉及数据安全问题

    @Override
    public void run() {
        account.draw(this.drawAmount);
    }
}

测试类

package thread._16_6;



/**
 * @author Mike
 * @use 银行取钱测试类
 */
public class DrawTest {
    public static void main(String[] args) {
        Account account = new Account("1234567" , 1000);
        new DrawThread("甲" ,account ,800).start();
        new DrawThread("乙",account,600).start();
    }
}

其实到这里我们又发现了一个问题
同步代码块相当于是厕所(恶臭的好例子),一个人进去,另外一个人只有等他出来才能进去。
假如有这样一种情况,手纸是另一个同步代码块,甲拿了纸,想进厕所,乙在厕所,没带纸想用纸。哦吼,这就是个死锁了。关于死锁的问题下一次复习。

发布了13 篇原创文章 · 获赞 10 · 访问量 621
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章