线程安全问题:多线程访问同一数据,容易出现线程安全问题
经典问题:银行取钱,1.用户名密码登陆,2.输入取款金额,3.判断账户余额是否大于取款金额,4.如果大于,取款成功,如果小于,取款失败。
public class Account { private String account;//账号 private double balance;//余额 public Account() { super(); } public Account(String account, double balance) { super(); this.account = account; this.balance = balance; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } }
public class DrawThread extends Thread{ private Account account;//模拟账户 private double drawMoney;//当前取钱线程想要取的钱数 public DrawThread(String name , Account account, double drawMoney) { super(name); this.account = account; this.drawMoney = drawMoney; } public void run() { if(account.getBalance() >= drawMoney) { System.out.println(getName() + "取钱成功!吐出钞票:" + drawMoney); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } account.setBalance(account.getBalance() - drawMoney); System.out.println(" 余额为:" + account.getBalance()); }else { System.out.println(getName() + "取钱失败!余额不足"); } } public static void main(String[] args) { Account account = new Account("123456" , 1000); new DrawThread("A", account, 800).start(); new DrawThread("B", account, 800).start(); } }
同步代码块:上面Demo中,两个并发线程在修改Account对象,很容易造成异常;为了解决这样的异常,java支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块,同步代码块的语法:
synchronized(Obj){
...//同步代码块
}
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成之后,该线程会释放对该同步监视器的锁定。
虽然java程序允许任何对象作为同步监视器,但是同步监视器的目的:是为了阻止两个线程对一个共享资源进行并发访问,因而推荐使用可能被并发访问的共享资源充当同步监视器。
public void run() { //使用account作为同步监视器,任何线程进入同步代码块之前必须获得对account的锁定,其他线程无法解锁,也无法修改它 //加锁 --> 执行代码块 -->释放锁 synchronized(account) { if(account.getBalance() >= drawMoney) { System.out.println(getName() + "取钱成功!吐出钞票:" + drawMoney); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } account.setBalance(account.getBalance() - drawMoney); System.out.println(" 余额为:" + account.getBalance()); }else { System.out.println(getName() + "取钱失败!余额不足"); } } }