【Java并发编程常见错误】多个资源共享的保护

【Java并发编程常见错误】多个资源共享的保护,第1张

文章目录
  • 1. 业务场景
  • 2. 问题分析
  • 3. 注意锁的对象

1. 业务场景

简单模拟一个A账户像B账户转账的场景,A账户减少1元,B账户就增加1元。

代码实现如下,为了演示多线程共享资源竞争的问题,使用CountDownLatch模拟并发。

public class Test_01 {

    public static final CountDownLatch start = new CountDownLatch(1);
    public static final CountDownLatch done = new CountDownLatch(10);

    public static void main(String[] args) throws InterruptedException {

        Account a = new Account();
        a.setBalance(new BigDecimal("100"));

        Account b = new Account();
        b.setBalance(new BigDecimal("0"));

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    start.await();
                    a.transfer(b, new BigDecimal("1"));
                    done.countDown();
                }
            }).start();

        }
        start.countDown();
        done.await();
        System.out.println("A账户余额:" + a.getBalance());
        System.out.println("B账户余额:" + b.getBalance());

    }

}

@Data
class Account {

    private BigDecimal balance;


    public void transfer(Account target, BigDecimal amount) {
        if (this.balance.compareTo(amount) >= 0) {
            this.balance = balance.subtract(amount);
            target.balance = target.balance.add(amount);
        } else {
            System.out.println("余额不足!");
        }

    }
}

执行可能出现如下结果。

2. 问题分析

问题很明显,肯定是transfer方法中对balance这个共享资源的访问出现了问题,此时,最容易想到的解决方式就是在transfer上加上synchronized关键字。

public synchronized void transfer(Account target, BigDecimal amount) {
    if (this.balance.compareTo(amount) >= 0) {
        this.balance = balance.subtract(amount);
        target.balance = target.balance.add(amount);
    } else {
        System.out.println("余额不足!");
    }
}

的确,加上synchronized可以解决当前的问题,但如果现在是3个账户转钱呢?

比如像下面这样,ab转钱,然后b又把钱转给了c

public class Test_01 {


    public static final CountDownLatch start = new CountDownLatch(1);
    public static final CountDownLatch done = new CountDownLatch(10);

    public static void main(String[] args) throws InterruptedException {

        Account a = new Account();
        a.setBalance(new BigDecimal("100"));

        Account b = new Account();
        b.setBalance(new BigDecimal("0"));

        Account c = new Account();
        c.setBalance(new BigDecimal("0"));

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    start.await();
                    a.transfer(b, new BigDecimal("1"));
                    b.transfer(c, new BigDecimal("1"));
                    done.countDown();
                }
            }).start();
        }
        start.countDown();
        done.await();
        System.out.println("A账户余额:" + a.getBalance());
        System.out.println("B账户余额:" + b.getBalance());
        System.out.println("C账户余额:" + c.getBalance());

    }

}

@Data
class Account {

    private BigDecimal balance;


    public synchronized void transfer(Account target, BigDecimal amount) {
        if (this.balance.compareTo(amount) >= 0) {
            this.balance = balance.subtract(amount);
            target.balance = target.balance.add(amount);
        } else {
            System.out.println("余额不足!");
        }

    }
}

结果又错了

3. 注意锁的对象

为什么加了synchronized还会有问题呢?原因就在于锁的对象的问题,synchronized关键字加在非静态的方法上时,锁的是当前的实例对象,也就是this这个对象,而我们的transfer中实际上还包含了一个target对象,而这个对象中的balance则是没有受到保护的,因此出现了线程安全的问题。

问题找到了,解决方式就很容易,只需要找一个共享的对象作为锁即可。

public class Test_01 {


    public static final CountDownLatch start = new CountDownLatch(1);
    public static final CountDownLatch done = new CountDownLatch(10);

    public static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {

        Account a = new Account();
        a.setBalance(new BigDecimal("100"));

        Account b = new Account();
        b.setBalance(new BigDecimal("0"));

        Account c = new Account();
        c.setBalance(new BigDecimal("0"));

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    start.await();
                    a.transfer(b, new BigDecimal("1"), lock);
                    b.transfer(c, new BigDecimal("1"), lock);
                    done.countDown();
                }
            }).start();
        }
        start.countDown();
        done.await();
        System.out.println("A账户余额:" + a.getBalance());
        System.out.println("B账户余额:" + b.getBalance());
        System.out.println("C账户余额:" + c.getBalance());

    }

}

@Data
class Account {

    private BigDecimal balance;


    public void transfer(Account target, BigDecimal amount, Object lock) {
        synchronized (lock){
            if (this.balance.compareTo(amount) >= 0) {
                this.balance = balance.subtract(amount);
                target.balance = target.balance.add(amount);
            } else {
                System.out.println("余额不足!");
            }
        }
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/801351.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-07
下一篇 2022-05-07

发表评论

登录后才能评论

评论列表(0条)

保存