Synchronized的介绍及其用法

synchronized 关键字用于实现对象级别的同步,它可以保证多个线程在访问某个对象时的互斥性,避免并发访问导致的数据竞争和不一致

public class BankAccount {

    private BigDecimal balance;

    public BankAccount(String initialValue) {
        this.balance = new BigDecimal(initialValue);
    }

    /**
     * 存款
     */
    public synchronized void deposit(String amount) {
        BigDecimal depositAmount = new BigDecimal(amount);
        balance = balance.add(depositAmount);
    }

    /**
     * 取款
     */
    public synchronized void withdraw(String amount) {
        BigDecimal withdraw = new BigDecimal(amount);
        if (balance.compareTo(withdraw) >= 0) {
            balance = balance.subtract(withdraw);
        } else {
            throw new ServiceException("余额不足");
        }
    }

    /**
     * 查询余额
     */
    public synchronized String getBalance() {
        return balance.toString();
    }
}
    @Test
    void synchronizedMethod() {
        Assertions.assertDoesNotThrow(() -> {

            // 初始化一个0元余额的银行账户
            BankAccount bankAccount = new BankAccount("0");

            // 创建多个线程执行存款、取款
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 500000; i++) {
                    // 存款
                    bankAccount.deposit(String.valueOf(1));
                }
            });

            Thread t2 = new Thread(() -> {
                for (int i = 0; i < 500000; i++) {
                    // 取款
                    bankAccount.withdraw(String.valueOf(1));
                }
            });

            t1.start();
            // 阻塞主线程,使得t1线程执行完毕才会被主线程竞争到CPU
            try {
                t1.join();
            } catch (InterruptedException e) {
                log.error("InterruptedException, Cause by", e);
            }

            t2.start();
            // 阻塞主线程,使得t2线程执行完毕才会被主线程竞争到CPU
            try {
                t2.join();
            } catch (InterruptedException e) {
                log.error("InterruptedException, Cause by", e);
            }

            /*
                直接执行,无异常,输出Balance: 0
                    t1执行完毕,账户余额50万;t2执行完毕,账户余额0;主线程执行

                若将阻塞主线程的代码都注释,输出Balance: 0 && 抛出异常提示:余额不足
                    主线程优先执行完,输出的余额是初始化的余额;后续t1、t2竞争CPU资源执行各自代码

                注释第二次阻塞主线程代码,无异常,输出Balance: 500000
                    t1线程执行完毕;主线程执行完毕;最后执行t2线程

                注释第一次阻塞主线程代码,抛出异常提示:余额不足 && 输出Balance: 1624
                    t2线程执行+抛出异常+结束;主线程竞争到CPU执行完毕;t1线程执行顺序不确定(可能最开始执行了,可能在t2和主线程之间执行了,可能在最后也执行了)
             */
            log.info("Balance: " + bankAccount.getBalance());
        });
    }

以下是一些关于synchronized的观点:

  1. 锁粒度较大:使用synchronized关键字时,通常需要锁定整个方法或代码块,这可能会降低并发性能。对于细粒度的锁需求,可以考虑使用更灵活的锁机制,例如ReentrantLock等。

  2. 可能导致死锁:如果对于多个资源或锁的请求没有正确处理,就有可能导致死锁情况的发生。避免死锁需要谨慎地设计和管理锁的使用。

  3. 可能导致性能问题:由于synchronized是独占锁,可能会导致线程竞争和等待的情况,进而影响程序的执行效率。在高并发场景下,可能需要考虑使用更高级的并发工具,如ConcurrentHashMap或并发集合类来提高性能。

  4. 更多选择:随着Java并发编程领域的不断发展,出现了许多更高级的并发工具和框架,例如AQS(AbstractQueuedSynchronizer)、java.util.concurrent包中的工具类等,这些工具提供了更多的灵活性和功能,使得开发者能够更好地处理并发编程中的各种问题。

posted @ 2023-08-30 15:40  Ashe|||^_^  阅读(33)  评论(0编辑  收藏  举报