多线程(63)处理过的并发问题

并发问题示例,并展示如何解决它。这个例子将涉及到一个常见的并发问题------竞态条件(Race Condition),并展示如何使用Java的synchronized关键字来解决这个问题。

竞态条件示例

假设有一个简单的银行账户类,它允许存款、取款和查询余额:

java 复制代码
public class BankAccount {
    private int balance;

    public BankAccount(int balance) {
        this.balance = balance;
    }

    public void deposit(int amount) {
        balance += amount;
    }

    public void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }

    public int getBalance() {
        return balance;
    }
}

在单线程环境中,这个类的行为是没有问题的。但是,如果在多线程环境中同时执行depositwithdraw方法,就会出现竞态条件,导致balance的值不正确。

演示竞态条件

假设初始余额为100,两个线程同时对同一个BankAccount实例执行操作:一个线程存入50,另一个线程取出50。理论上,这些操作完成后余额应该仍然是100。但是由于竞态条件的存在,实际结果可能并非如此。

这是因为操作系统可能在任一操作执行的中间阶段切换线程,例如,在一个线程计算新的余额但尚未将其写回balance字段时,另一个线程可能读取了旧的balance值,从而导致不一致。

解决方案:使用synchronized

为了解决这个问题,我们可以使用synchronized关键字来保证同一时间只有一个线程能执行修改balance的操作。我们将depositwithdraw方法标记为synchronized

java 复制代码
public class BankAccount {
    private int balance;

    public BankAccount(int balance) {
        this.balance = balance;
    }

    public synchronized void deposit(int amount) {
        balance += amount;
    }

    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }

    public int getBalance() {
        return balance;
    }
}

现在,即使在多线程环境中,每次只有一个线程可以执行这些同步方法。如果一个线程正在执行一个同步方法,其他线程试图执行任何同步方法都会被阻塞,直到第一个线程完成操作。

演示代码

下面是一个模拟上述行为的测试类:

java 复制代码
public class BankAccountTest {

    public static void main(String[] args) throws InterruptedException {
        final BankAccount account = new BankAccount(100);

        Thread t1 = new Thread(() -> {
            account.deposit(50);
            System.out.println("After deposit, balance is: " + account.getBalance());
        });

        Thread t2 = new Thread(() -> {
            account.withdraw(50);
            System.out.println("After withdrawal, balance is: " + account.getBalance());
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Final balance is: " + account.getBalance());
    }
}

这个测试类将创建两个线程,分别执行存款和取款操作,然后输出最终的余额。由于我们已经将depositwithdraw方法同步了,所以最终的余额应该始终是100。

这个简单的例子演示了如何识别并解决一个常见的并发问题。在实际的多线程应用程序开发中,确保线程安全通常比这个例子中展示的要复杂得多。设计线程安全的类可能需要仔细考虑和应用并发模式和Java并发工具类。

相关推荐
GraduationDesign5 分钟前
基于SpringBoot的蜗牛兼职网的设计与实现
java·spring boot·后端
颜淡慕潇16 分钟前
【K8S问题系列 | 20 】K8S如何删除异常对象(Pod、Namespace、PV、PVC)
后端·云原生·容器·kubernetes
customer0820 分钟前
【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)
java·vue.js·spring boot·后端·kafka·开源·旅游
搬码后生仔1 小时前
将 ASP.NET Core 应用程序的日志保存到 D 盘的文件中 (如 Serilog)
后端·asp.net
Suwg2092 小时前
《手写Mybatis渐进式源码实践》实践笔记(第七章 SQL执行器的创建和使用)
java·数据库·笔记·后端·sql·mybatis·模板方法模式
凡人的AI工具箱2 小时前
每天40分玩转Django:Django文件上传
开发语言·数据库·后端·python·django
spcodhu3 小时前
在 Ubuntu 上搭建 MinIO 服务器
linux·后端·minio
小码编匠3 小时前
2024 年各编程语言运行百万并发任务需多少内存?
java·后端·python
小码编匠3 小时前
C# 实现多线程启动停止暂停继续
后端·c#·.net
sin22014 小时前
springboot测试类里注入不成功且运行报错
spring boot·后端·sqlserver