Java线程同步实战:解决多线程共享资源冲突

Mason
Mason
发布时间:2026-10-13| 2人浏览过
原创

在Java多线程编程中,多个线程同时访问共享资源(如账户余额)极易导致数据竞争和结果错误。本文将通过一个模拟银行多窗口存取款的具体案例,展示如何利用`synchronized`关键字获取对象锁,强制线程串行执行关键代码块,从而保证并发环境下的数据一致性。

线程同步的必要性与原理

多线程为程序带来性能提升的同时,也引入了复杂度。当多个线程读写同一份共享数据时,若不加控制,会出现“脏读”或数据覆盖现象。例如在多人同时查看并修改同一张餐桌上的食物(共享资源)时,最终状态可能不符合预期。

Java提供了synchronized(同步化)修饰符来解决此问题。每个继承自Object的类默认都包含一个内置机锁(Lock)。当某个线程调用对象的`synchronized`方法或进入`synchronized`代码块时,该对象将被锁定,其他线程试图进入该对象任何同步区域时都必须等待,直到当前线程释放锁。这确保了同一时刻只有一个线程能执行被保护的代码段。

案例准备:构建并发交易环境

为了直观观察线程同步的效果,我们设计了一个基于Swing的图形界面应用程序。该程序模拟了多个银行网点(线程)对同一个银行账户进行的并发存款和取款操作。

  • 核心目标:验证在多线程高并发下,账户余额计算是否准确。
  • 主要组件:
  • TestMain5: 主窗体类,负责布局UI并启动交易线程。
  • Bank: 内部线程类,模拟不同支行的业务处理。
  • MyAccounts: 账号管理类,包含实际的余额数据及同步逻辑。

真实操作顺序:同步代码实现

第一步:定义共享数据容器(MyAccounts)

此类封装了具体的业务逻辑。关键在于使用synchronized (count)包裹对变量的读取、修改和写入过程。虽然此处锁住了Integer包装类型,但在实际生产环境中,建议锁住专用的私有锁对象(如private final Object lock = new Object();),以避免对不可变对象本身的意外操作。

注意:inquire(), saving(), 和 fetch() 方法均被同步保护,防止中途状态被其他线程窥探。

第二步:模拟多线程并发触发(Bank类)

Bank类继承自Thread,代表不同的交易大厅。构造函数中直接调用this.start()立即开启新线程。

  • 常量定义:DEAL_SAVING表示存款,DEAL_FETCH表示取款。
  • 执行逻辑:根据传入的类型调用账号类的对应方法,并在耗时操作中(如Thread.sleep(1000))保持锁的状态,以便观察并发阻塞效果。

第三步:主流程与UI联动(TestMain5类)

在主窗体初始化时,设置初始余额为1100。点击"开始交易"按钮后,瞬间创建6个银行线程,分别进行不同金额的存/取款操作。由于加了同步锁,这些操作将排队依次执行,最终余额应为各笔交易的代数和。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
 * 线程同步测试主类
 */
public class TestMain5 extends JFrame {
 private MyAccounts myAccounts = null; // 共享账号资源
 private JTextField text = null; // 显示当前余额
 private JTextArea textArea = null; // 显示交易流水日志
 private JButton button = null; // 触发动作按钮
 
 /**
  * 构造一个银行存取款模拟界面
  */
 public TestMain5(){
  super("线程同步测试");
  myAccounts = new MyAccounts();
  // 初始余额设为1100
  text = new JTextField(Integer.toString(myAccounts.inquire()), 10); 
  textArea = new JTextArea();
  textArea.setText("交易日志:\n");
  JScrollPane sp = new JScrollPane(textArea);
  
  button = new JButton("开始交易");
  button.addActionListener(new ActionListener(){
   public void actionPerformed(ActionEvent e) {
    // 模拟6家支行同时进行业务
    new Bank("钟楼支行", myAccounts, Bank.DEAL_SAVING, 800);
    new Bank("高新支行", myAccounts, Bank.DEAL_SAVING, 1300);
    new Bank("小寨支行", myAccounts, Bank.DEAL_FETCH, 200);
    new Bank("雁塔支行", myAccounts, Bank.DEAL_FETCH, 400);
    new Bank("兴庆支行", myAccounts, Bank.DEAL_SAVING, 100);
    new Bank("土门支行", myAccounts, Bank.DEAL_FETCH, 700);
   }
  });
  
  JPanel pane = new JPanel();
  pane.add(text);
  pane.add(button);
  this.getContentPane().add(pane, BorderLayout.NORTH);
  this.getContentPane().add(sp);
  this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  this.setSize(300, 200);
  this.setLocationRelativeTo(null);
  this.setVisible(true);
 }
 
 /**
  * 银行交易大厅线程类
  */
 class Bank extends Thread{
  public static final int DEAL_SAVING = 0; // 存款
  public static final int DEAL_FETCH = 1;  // 取款
  
  private int actionType; // 动作类型
  private int amount;     // 金额
  private MyAccounts account; // 共享账号
  
  /**
   * 构造并立即启动线程
   */
  public Bank(String name, MyAccounts account, int actionType, int amount){
   super(name);
   this.account = account;
   this.actionType = actionType;
   this.amount = amount;
   this.start();
  }
  
  public void run(){
   int resultBalance = 0;
   String actionName = "";
   
   if(actionType == Bank.DEAL_SAVING){ 
    resultBalance = account.saving(amount);
    actionName = "存款";
   }else { 
    resultBalance = account.fetch(amount);
    actionName = "取款";
   }
   
   // 更新UI显示
   text.setText(Integer.toString(resultBalance));
   textArea.append(this.getName() + " " + actionName + " " + amount + " 结余:" + resultBalance + "\n");
  }
 }
 
 /**
  * 账号管理核心类(涉及同步)
  */
 class MyAccounts{
  private Integer balance = 1100; // 初始余额
  
  /**
   * 查询余额
   */
  public synchronized int inquire(){
   return balance;
  }
  
  /**
   * 存款业务
   */
  public synchronized int saving(int c){
   // 为保证可见性演示,逐步操作:先查、改、再写回
   int currentBalance = inquire(); 
   currentBalance += c;
   try {
    Thread.sleep(1000); // 模拟网络延迟或处理耗时
   } catch (InterruptedException ex) {
    ex.printStackTrace();
   }
   balance = currentBalance; 
   return inquire(); 
  }
  
  /**
   * 取款业务
   */
  public synchronized int fetch(int c){
   int currentBalance = inquire(); 
   currentBalance -= c;
   try {
    Thread.sleep(1000);
   } catch (InterruptedException ex) {
    ex.printStackTrace();
   }
   balance = currentBalance; 
   return inquire(); 
  }
 }
 
 public static void main(String [] args){
  new TestMain5();
 }
}

完成判断与总结

运行上述代码,点击"开始交易"后,观察控制台输出的日志。如果同步生效,每笔交易的余额更新将是有序的,且最终余额正确反映了所有存入和取出的总和。如果在MyAccounts中移除synchronized,你会发现日志中的余额跳动混乱,甚至出现负数或计算错误。

通过本例可以看出,synchronized是Java中最基础的互斥锁机制。它在简单场景下能有效防止竞态条件(Race Condition),但也带来了串行执行的开销。对于更高性能的并发需求,后续可探索java.util.concurrent包中的锁工具。以上就是Java线程同步实例分析的详细内容,更多关于Java多线程并发处理的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。