Java--多线程-线程安全

多个线程竞争同一资源时,会出现线程安全问题,例如银行取钱场景。

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讲义》。

你可能感兴趣的:(Java)