Java多线程:使用Lock解决售票并发安全问题

栗子
栗子
发布时间:2026-10-15| 5人浏览过
原创

在Java多线程开发中,同步锁用于解决资源竞争导致的安全问题。本文以经典的窗口售票为例,对比未加锁导致的重复票现象,并详细展示如何利用 ReentrantLock 进行加锁与解锁操作,确保数据一致性。

结果或目标:解决多线程数据竞争

当多个线程同时访问共享资源时,若没有适当的同步机制,会导致数据错乱。例如在多线程售票场景中,可能出现一张票被卖出多次的情况。我们的目标是引入一种机制,确保同一时刻只有一个线程能执行售票逻辑。

准备条件

要实现基于 Lock 接口的同步控制,需要以下基础:

  • JDK版本:建议使用 JDK 1.5 及以上版本,因为从该版本开始引入了 java.util.concurrent.locks 包。
  • 核心类:java.util.concurrent.locks.ReentrantLock 是 Lock 接口的常用实现类。
  • 业务场景:创建一个模拟售票的 Runnable 任务,包含余票数计数器。

真实操作顺序

第1步:重现无锁环境下的并发问题

首先,我们构建一个多线程售票模型,观察不加锁时的运行结果。此步骤旨在验证并发问题的存在性。

  1. 创建内部类 Ticket 实现 Runnable 接口,初始化总票数(如100张)。
  2. 在 run() 方法中编写循环逻辑,模拟每0.2秒卖出一张票的过程。
  3. 在主线程中创建三个子线程,分别命名为“1号窗口”、“2号窗口”和“3号窗口”,并传入同一个 Ticket 实例启动。

public class LockDemo1 {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket, "1号窗口").start();
        new Thread(ticket, "2号窗口").start();
        new Thread(ticket, "3号窗口").start();
    }

    static class Ticket implements Runnable {

        private int tick = 100;

        @Override
        public void run() {
            while (true) {
                try {
                    // 模拟售票间隔
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tick > 0) {
                    System.out.println(Thread.currentThread().getName() + "完成售票,余票是:" + --tick);
                }
            }
        }
    }
}

运行上述代码,控制台会出现明显的并发错误:部分票号为负数,或者多行输出显示相同的余票数(即超卖现象)。这是因为三个线程同时读取了相同的 tick 值并同时进行递减操作,缺乏原子性保护。

第2步:引入 Lock 实现同步控制

为了解决上述问题,我们需要引入显式锁机制。与 synchronized 不同,Lock 提供了更灵活的控制能力,但要求开发者手动管理锁的生命周期。

  1. 实例化锁对象:在 Ticket 类中声明并初始化 private Lock lock = new ReentrantLock();。
  2. 获取锁:在执行核心业务逻辑前,调用 lock.lock(); 获取锁。这会阻塞其他试图进入该代码段的线程。
  3. 释放锁:使用完毕后必须释放锁。**关键规范**:释放锁的操作必须放在 finally 块中,以确保即使发生异常,锁也能被正确释放,避免死锁。

第3步:重构代码以应用 ReentrantLock

基于第一步的代码,增加锁逻辑后得到修正后的版本:


public class LockDemo2 {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket, "1号窗口").start();
        new Thread(ticket, "2号窗口").start();
        new Thread(ticket, "3号窗口").start();
    }

    static class Ticket implements Runnable {

        private int tick = 100;
        // 创建可重入锁实例
        private Lock lock = new ReentrantLock();

        @Override
        public void run() {
            while (true) {
                // 上锁
                lock.lock();
                try {
                    // 业务逻辑:模拟耗时操作及判断
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (tick > 0) {
                        System.out.println(Thread.currentThread().getName() + "完成售票,余票是:" + --tick);
                    }
                } finally {
                    // 必须释放锁,防止死锁
                    lock.unlock();
                }
            }
        }
    }
}

在修改后的代码中,每一次售票操作都被包裹在 lock.lock() 和 finally { lock.unlock(); } 之间。这意味着只要有一个线程正在执行售票,其他线程就必须等待,从而保证了 --tick 操作的原子性。

再次运行代码,可以观察到输出结果中票号递减有序,不再出现负数票或重复票,成功解决了并发安全问题。

完成判断与优势分析

完成判断标准:程序运行时控制台打印的余票数严格递减且无负数,无卡死现象(证明锁能正常释放)。

Lock vs synchronized:相比传统的 synchronized 关键字,Lock 具有以下显著优势:

  • 灵活性:开发者可以自行控制加锁和解锁的时机,支持非阻塞尝试获取锁(tryLock)和定时等待,更适合复杂的并发场景。
  • 状态感知:可以通过 isLocked() 等方法查询锁的状态。
  • 公平性:在构造函数中指定 true 参数即可创建公平锁,按请求顺序分配锁,避免线程饥饿。

简要总结

在Java并发编程中,ReentrantLock 是一种强大且灵活的同步工具。它通过显式的 lock() 和 unlock() 方法配合 finally 块,有效解决了多线程环境下的数据竞争问题。使用时务必注意资源的释放,以避免不可预知的系统故障。

以上就是Java多线程中使用Lock同步锁解决并发安全的详细内容,更多关于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 算法库。