开关用 volatile,排队用 synchronized,复杂用 Lock:一文理清并发三兄弟
在 Java 并发编程中volatile、synchronized和Lock是最常用的三种同步机制。很多人能说出它们的区别却说不清各自最适合用在什么地方。这篇文章不用代码只从“解决什么问题”的角度帮你彻底理清它们的关系与运用场景。一、先一句话概括各自角色关键字/接口一句话概括本质volatile保证变量修改的可见性和有序性但不保证原子性JVM 轻量级同步机制synchronized保证代码块的原子性、可见性、有序性基于 monitor 机制内置锁重量级可优化Lock是synchronized的 API 版本提供更灵活的控制可中断、可超时、公平锁等显式锁JUC 提供二、它们的关系是递进的volatile是基础它只解决了“一个线程写多个线程读”的可见性问题。无法解决多个线程同时写如count的原子性问题。synchronized是升级用锁把代码块包起来同一时刻只允许一个线程执行。但它较重获取锁的线程必须阻塞等待且不能中断、不能超时。Lock是增强提供了synchronized没有的能力可中断、可超时、公平锁、多个等待队列Condition。功能强弱/灵活度volatilesynchronizedLock使用复杂度volatilesynchronizedLock性能现代 JVMvolatile≈synchronized优化后 ≈LockCAS 实现三、一句话概括它们各自负责什么volatile我改了你必须立刻看到 ——可见性synchronized一次只让一个人进来别人在外面等着 ——互斥 可见性Lock像synchronized一样互斥但我可以随时不排队、中途走人、叫号公平点 ——更灵活的互斥四、volatile 用在哪儿核心场景一个线程写多个线程读的状态标志volatile不保证原子性不能用于count。它适合表达“事情发生了 / 状态变了”其他线程需要立刻感知。典型运用开关控制状态标志后台轮询线程需要一个running变量控制它停止。服务关闭时主线程设置running false工作线程看到后立即退出循环实现优雅停机。双重检查锁DCL实现单例高并发下只创建一个对象实例。volatile阻止指令重排序防止其他线程拿到未初始化完成的对象。读多写少的共享变量配置项、系统参数偶尔修改但大量读取。例如config.refreshInterval修改后所有工作线程立刻用上新值。五、synchronized 用在哪儿核心场景大家都可能改必须排队的原子性操作volatile解决不了“同时写”的问题需要synchronized把代码块变成原子操作。典型运用计数器、累加器统计接口调用次数、在线人数、库存扣减。increment()方法必须排队执行否则多线程同时 1 会丢失计数。懒汉式单例整个方法或代码块加锁懒加载且保证只创建一个实例。直接锁住getInstance()方法虽然效率低但实现最简单。复合操作先检查后执行如if (map.containsKey(key))再map.get(key)。转账业务需要先判断余额再扣款这两步必须一起锁住。操作非线程安全的集合多线程并发操作HashMap、ArrayList。维护一个缓存 Map 时增删改查都锁住对象防止读的时候有人删导致ConcurrentModificationException。JVM 层面的唯一操作wait/notify生产者-消费者模型中wait()/notify()必须在synchronized块里调用。六、Lock 用在哪儿核心场景synchronized 功能不够用的地方Lock提供了synchronized无法实现的精细控制尝试加锁、超时加锁、可中断加锁。典型运用尝试获取锁拿不到锁就不想等直接做别的事或返回错误。秒杀系统中尝试获取锁拿不到就直接告诉用户“太挤了稍后再试”而不是让用户卡死。可中断的锁锁等待时间可能很长用户想主动取消操作。例如用户在 GUI 界面点击“取消搜索”需要打断正在等待数据库锁的线程。带超时的锁避免某个线程异常导致其他线程无限等待。分布式任务调度中抢锁的线程必须在 10 秒内完成任务并释放锁否则锁自动失效。公平锁严格按先来后到获取锁防止饥饿。例如银行叫号系统理想情况下不允许插队。多个等待队列Condition一个锁配合多个等待条件。有界阻塞队列中队列空时取元素线程等待“非空”信号队列满时存元素线程等待“未满”信号。一个锁配两个Condition代码更清晰。七、总结对比运用场景机制核心战场典型应用一句话场景volatile状态标志开关控制、DCL 单例一个线程改其他线程立刻看synchronized原子操作计数器、复合操作、wait/notify多个线程改必须排队Lock高级控制尝试锁、超时锁、可中断锁synchronized不够灵活时最后一句口诀帮你记住开关用 volatile简单排队用 synchronized复杂控制超时、中断、公平用 Lock。如果你觉得这篇文章对你有帮助欢迎点赞、收藏、转发让更多人理清 Java 并发中的这三个关键角色。