Java多线程同步锁卖票实战:synchronized与Lock的区别
在Java并发编程中,多线程共享数据是引发“脏数据”的主要原因。本文将通过经典的“卖票”实战项目,演示如何使用同步锁解决资源竞争问题。文章包含无锁情况下的错误结果分析、使用synchronized的正确实现方式,以及同步锁内部原理的图解,最后对比synchronized关键字与Lock接口的区别,为开发者提供完整的线程同步解决方案。
什么是线程同步?
当多个线程同时访问同一个资源(如共享变量)时,如果不进行协调,可能导致数据不一致。例如,多个窗口同时卖出同一张票。线程同步旨在确保在同一时刻,只允许一个线程访问该临界资源,从而保证数据的准确性和操作的原子性。
第1步:创建售票任务类(Runnable)
为了实现多线程模拟,我们需要创建一个实现Runnable接口的类。在这个类中,我们定义总票数,并在run方法中编写售票逻辑。为了保证线程安全,必须使用synchronized(this)对共享资源tickets进行加锁保护。
关键参数说明:
int tickets = 20:模拟总票数,作为共享变量。synchronized (this):获取当前对象实例的监视器锁,确保同一时间只有一个线程执行花括号内的代码。Thread.sleep(200):模拟人工售票耗时,便于观察多线程交错执行的效果。
public class Ticket implements Runnable{
int tickets=20;//总共20张票
@Override
public void run() {
while (tickets>0) {
synchronized (this) {
if(tickets>0) {
//售票
try {
System.out.println(Thread.currentThread().getName()+"正在售票");
Thread.sleep(200);//模拟售票操作时间
tickets--;//完成售票
System.out.println(Thread.currentThread().getName()+"售票完成,"+"剩余"+tickets+"张票");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
第2步:编写测试启动类
在主类中,创建Ticket类的单一实例。随后创建三个线程,传入同一个Ticket实例,模拟三个窗口同时售票的场景。每个线程启动后,将独立执行run方法,但受同步锁约束。
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//模拟三个窗口售票
Thread t1=new Thread(ticket);
t1.setName("窗口1");
t1.start();
Thread t2=new Thread(ticket);
t2.setName("窗口2");
t2.start();
Thread t3=new Thread(ticket);
t3.setName("窗口3");
t3.start();
}
}运行结果对比分析
1. 加入同步锁后的正常结果
由于使用了synchronized,线程按顺序逐一执行。可以看到输出日志严格遵循“窗口X正在售票 -> 窗口X售票完成 -> 剩余N张票”的逻辑,且票数递减无误(20->19...->0),没有重复售卖或跳号现象。
窗口1正在售票
窗口1售票完成,剩余19张票
窗口1正在售票
窗口1售票完成,剩余18张票
...
窗口2正在售票
窗口2售票完成,剩余0张票
2. 不加同步锁的异常结果
如果移除synchronized块,多线程将无序抢占CPU资源。典型错误包括:
- 超卖:票数减到负数。
- 重卖:多张票被标记为同一编号售出,或者同一票数被多次打印出售出信息。
从日志可见,"窗口1完成"和"窗口3完成"可能产生相同的剩余票数,或者出现"剩余19张票"后紧接着又出现"剩余19张票",这显然是数据竞争导致的严重Bug。
窗口3正在售票
窗口1正在售票
窗口2正在售票
窗口1售票完成,剩余18张票
窗口1正在售票
窗口2售票完成,剩余19张票
...
synchronized与Lock锁的核心区别
虽然本例使用synchronized成功解决了问题,但在更复杂的并发场景中,开发者常会对比synchronized关键字与java.util.concurrent.locks.Lock接口的区别:
- 层级不同:
synchronized是JVM层面的关键字,由虚拟机自动获取和释放锁;而Lock是API级别的工具类(如ReentrantLock),需要手动调用lock()和unlock()。 - 灵活性不同:
Lock支持尝试获取锁(tryLock)、中断获取锁等高级功能,synchronized则只能阻塞等待。 - 性能差异:在高并发激烈竞争下,旧版JDK中
synchronized性能较差,但经过版本迭代(偏向锁、轻量级锁优化后),两者性能差距已大幅缩小。通常建议优先使用synchronized,除非需要Lock提供的特殊控制能力。
以上就是Java多线程实现同步锁卖票实战项目的详细内容,更多关于Java并发编程中线程安全与锁机制的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









