多个线程竞争同一资源时,会出现线程安全问题,例如银行取钱场景。
public class Account {
private String accountNo;
private Integer balance;
public Account(String accountNo, Integer balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
//省略get、set方法
}
取钱操作:
public class DrawThread extends Thread {
private Account account;
private Integer drawAmount;
public DrawThread(String name, Account account, Integer drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
if (account.getBalance() >= this.drawAmount) {
// 余额足够
System.out.println("吐出钞票");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额
account.setBalance(account.getBalance() - this.drawAmount);
System.out.println("余额为" + account.getBalance());
} else {
// 余额不够
System.out.println("余额不足");
}
}
}
起两个线程,模拟并发取钱操作。
public class TextMain {
public static void main(String[] args) {
Account account = new Account("张三", 1000);
new DrawThread("甲", account, 800).start();
new DrawThread("已", account, 800).start();
}
}
如图中,账户余额为负数,出现该情况的原因为第一个线程开始取钱,进入后当前线程睡眠,但是没有修改该账户的余额,导致第二个取钱线程能够进入到能取钱的操作,第一个线程执行减少余额操作,第二个线程也执行减少余额操作。导致余额出现了负数。
1 解决方案–同步代码块:
解决该问题的思路为在进入取钱操作时添加同步监视器,线程在执行之前必须获得对同步监视器的锁定。
阻止两个线程对同一共享资源的并发访问。实现加锁-》修改-》释放锁的逻辑。
改进后的取钱操作:
@Override
public void run() {
synchronized (this.account) {
if (account.getBalance() >= this.drawAmount) {
// 余额足够
System.out.println("吐出钞票");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额
account.setBalance(account.getBalance() - this.drawAmount);
System.out.println("余额为" + account.getBalance());
} else {
// 余额不够
System.out.println("余额不足");
}
}
}
修改为同步代码块后,监视account对象,保证当前只有一个线程能对account对象执行修改操作。
2 取钱操作设置为同步方法。
使用synchronize关键字,无需显式的指定同步监视器,同步方法的同步监视器即为this,即吊用该方法的对象。
同一场景,使用同步方法。
public class Account {
private String accountNo;
private Integer balance;
public Account(String accountNo, Integer balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
//同步方法
public synchronized void draw(Integer drawAmount) {
if (this.balance >= drawAmount) {
// 余额足够
System.out.println("吐出钞票");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改余额
this.balance = this.balance - drawAmount;
System.out.println("余额为" + this.balance);
} else {
// 余额不够
System.out.println("余额不足");
}
}
}
取钱线程:
public class DrawThread extends Thread {
private Account account;
private Integer drawAmount;
public DrawThread(String name, Account account, Integer drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
account.draw(800);
}
}
测试方法:
public class TextMain {
public static void main(String[] args) {
Account account = new Account("张三", 1000);
new DrawThread("甲", account, 800).start();
new DrawThread("已", account, 800).start();
}
}
参考《疯狂java讲义》。