Java多线程共享账户如何实现同步机制

来源:AI编程作者:上海网站建设头衔:草根站长
导读:本期聚焦于小伙伴创作的《Java多线程共享账户如何实现同步机制》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Java多线程共享账户如何实现同步机制》有用,将其分享出去将是对创作者最好的鼓励。

Java多线程环境下,多个线程同时操作同一个共享账户时,由于线程执行的随机性,会出现竞态条件,导致账户余额计算错误。比如两个线程同时读取账户余额进行取款操作,最后账户余额可能出现负数或者与实际操作不符的情况,因此需要通过同步机制保证共享账户操作的原子性和可见性。

Java多线程共享账户如何实现同步机制

共享账户并发问题示例

首先我们定义一个简单的共享账户类,包含账户余额属性和存取款方法,在不加同步控制的情况下,启动多个线程同时操作该账户,观察出现的问题。

// 共享账户类
class SharedAccount {
    // 账户余额
    private int balance;

    public SharedAccount(int initialBalance) {
        this.balance = initialBalance;
    }

    // 取款方法,未加同步控制
    public void withdraw(int amount) {
        if (balance >= amount) {
            // 模拟业务处理耗时
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
        } else {
            System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
        }
    }

    // 获取当前余额
    public int getBalance() {
        return balance;
    }
}

public class AccountTest {
    public static void main(String[] args) {
        // 初始化账户,余额1000
        SharedAccount account = new SharedAccount(1000);
        // 创建两个线程同时取款800
        Thread t1 = new Thread(() -> account.withdraw(800), "线程A");
        Thread t2 = new Thread(() -> account.withdraw(800), "线程B");
        t1.start();
        t2.start();
        // 等待两个线程执行完成
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终账户余额:" + account.getBalance());
    }
}

上述代码运行后,可能出现两个线程都取款成功的情况,最终余额变为-600,这显然不符合实际业务逻辑,这就是没有同步控制导致的并发问题。

基于synchronized的同步方案

synchronized是Java内置的同步关键字,可以用来修饰方法或者代码块,保证同一时间只有一个线程能执行被修饰的代码,从而解决共享资源的并发问题。

修饰实例方法

将共享账户的取款方法用synchronized修饰,此时锁对象就是当前账户实例,同一时间只有一个线程能调用该实例的取款方法。

class SyncAccount1 {
    private int balance;

    public SyncAccount1(int initialBalance) {
        this.balance = initialBalance;
    }

    // 用synchronized修饰实例方法,锁对象为当前实例
    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
        } else {
            System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
        }
    }

    public int getBalance() {
        return balance;
    }
}

修饰代码块

如果不需要对整个方法加锁,可以使用synchronized代码块,指定锁对象,减少锁的粒度,提升性能。

class SyncAccount2 {
    private int balance;
    // 自定义锁对象
    private final Object lock = new Object();

    public SyncAccount2(int initialBalance) {
        this.balance = initialBalance;
    }

    public void withdraw(int amount) {
        // 同步代码块,锁对象为自定义的lock实例
        synchronized (lock) {
            if (balance >= amount) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                balance -= amount;
                System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
            } else {
                System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
            }
        }
    }

    public int getBalance() {
        return balance;
    }
}

基于Lock接口的同步方案

Java并发包中的Lock接口提供了比synchronized更灵活的同步控制,其中ReentrantLock是可重入的互斥锁,使用起来更加可控。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class LockAccount {
    private int balance;
    // 创建可重入锁实例
    private final Lock lock = new ReentrantLock();

    public LockAccount(int initialBalance) {
        this.balance = initialBalance;
    }

    public void withdraw(int amount) {
        // 获取锁
        lock.lock();
        try {
            if (balance >= amount) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                balance -= amount;
                System.out.println(Thread.currentThread().getName() + " 取款成功,取款金额:" + amount + ",当前余额:" + balance);
            } else {
                System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
            }
        } finally {
            // 释放锁,避免死锁
            lock.unlock();
        }
    }

    public int getBalance() {
        return balance;
    }
}

不同同步机制对比

我们可以通过下表对比synchronized和ReentrantLock的特点,根据实际场景选择合适的同步方案:

对比维度synchronizedReentrantLock
实现方式Java内置关键字,JVM层面实现Java代码实现,基于AQS框架
锁的释放自动释放,方法执行完成或抛出异常时释放手动释放,需要在finally块中调用unlock方法
灵活性较低,只能修饰方法或代码块,无法中断等待锁的线程较高,支持尝试获取锁、超时获取锁、可中断获取锁
公平性非公平锁支持公平锁和非公平锁,构造时传入true为公平锁

同步机制使用注意事项

  • 锁对象的选择:尽量使用不可变的私有对象作为锁,避免使用字符串常量或者this作为锁,防止锁被外部代码意外获取导致问题。
  • 锁的粒度控制:不要随意扩大锁的范围,尽量只锁住操作共享资源的核心代码,减少线程阻塞的时间,提升并发性能。
  • 避免死锁:如果多个线程需要获取多个锁,要保证获取锁的顺序一致,同时Lock接口支持超时获取锁,可以有效避免死锁问题。
  • 可见性保证:同步机制不仅能保证操作的原子性,还能保证变量的可见性,一个线程修改共享变量后,其他线程能立即看到修改后的值。

在实际的共享账户场景中,根据业务的复杂度和性能要求选择合适的同步机制,就能有效避免多线程操作带来的数据不一致问题,保障账户数据的安全性。

Java多线程共享账户同步机制synchronizedLock修改时间:2026-06-15 06:18:34

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。