Java线程同步实战:解决多线程共享资源冲突
在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多线程并发处理的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













