栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录
  • 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个账户转钱呢?

比如像下面这样,a给b转钱,然后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("余额不足!");
            }
        }
    }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/851033.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号