在 Java 中,锁的类型可以从多个维度进行分类。
一、根据锁的性质划分
1. 内部锁(Intrinsic Lock)
Java 内置的锁机制,通常使用 synchronized 关键字实现。
每个对象都有一个内部锁,线程可以通过 synchronized 进入临界区。
特点:自动释放,不需要手动解锁。
2. 显示锁(Explicit Lock)
常用类有 ReentrantLock、ReadWriteLock 等。
特点:提供更灵活的锁机制,可以实现可重入锁、读写锁等,支持公平性控制。
3. 自旋锁(Spin Lock)
通过自旋等待(即不断循环尝试获取锁)而不是阻塞线程来实现的锁。
Java 本身没有直接的自旋锁类,但可以通过 Atomic 操作和 CAS 实现。
特点:适合短时间所持有的场景,减少线程上下文切换的开销。
4. 可重入锁(Reentrant Lock)
Java 提供的 ReentrantLock 类是典型的可重入锁。
特点:同一个线程可以多次获得同一把锁,避免死锁。
5. 读写锁(ReadWriteLock)
读写分离的锁,通过 ReadWriteLock 接口实现,主要实现类是 ReentrantReadWriteLock。
特点:允许多个线程同时读,但写操作需要独占锁,适用于读多写少的场景。
6. 公平锁和非公平锁(Fair and Unfair Lock)
ReentrantLock 可以配置为公平锁或非公平锁。
公平锁遵循先进先出原则,非公平锁则不保证顺序。
特点:公平锁减少了线程饥饿,但性能可能稍差;非公平锁可能提高性能,但有可能导致线程饥饿。
7. 偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)和重量级锁(Heavyweight Locking)
这些是 JVM 优化 synchronized 锁的几种状态。
偏向锁:当只有一个线程访问同步块时,锁会偏向该线程,减少加锁开销。
轻量级锁:在没有竞争时,会使用 CAS 操作代替传统的阻塞操作。
重量级锁:当有多个线程竞争时,锁会膨胀为重量级锁,通过操作系统的线程调度来管理。
8. 条件锁(Condition)
与 ReentrantLock 配合使用的锁,提供类似 Object.wait() 和 notify() 的功能。
常用类为 Condition。
特点:允许更灵活的线程等待和唤醒机制,适合复杂的同步需求。
9. 悲观锁(Pessimistic Lock)和乐观锁(Optimistic Lock)
悲观锁:假设会发生并发冲突,直接加锁保护资源。
乐观锁:假设不会发生并发冲突,采用 CAS 等无锁机制,通常使用 Atomic 类实现。
特点:悲观锁适合高竞争环境,乐观锁适合低冲突场景。
10. 信号量(Semaphore)
特点:不像普通的锁只能限制一个线程,可以设定多个许可。
11. 栅栏(Barrier)
用于多个线程间的协调和同步,例如 CyclicBarrier、CountDownLatch。
特点:适合一组线程需要在特定点上同步执行的场景。
12. 乐观锁(Optimistic Lock)
不是基于锁实现的,而是通过无锁的机制来确保并发安全。
常用 Atomic 类(如 AtomicInteger、AtomicReference)和 CAS 操作。
13. StampedLock
JDK 8 引入的锁,用于替代传统的读写锁,支持三种锁模式:写锁、悲观读锁和乐观读锁。
特点:乐观读锁允许读操作在没有竞争的情况下尽可能高效。
Java 中的锁机制种类丰富,涵盖了各种并发场景的需求。具体使用哪种锁,需要根据程序的并发特性、性能需求、资源访问模式等来选择。
二、根据锁的机制划分
14. 线程局部锁(ThreadLocal)
通过 ThreadLocal 类实现,使每个线程都能拥有自己的独立副本,从而避免竞争。
特点:不是真正的“锁”,但可以在需要隔离共享数据的情况下,避免线程间的同步开销。
15. 无锁数据结构(Lock-Free Data Structures)
使用 CAS 和原子类实现的无锁数据结构,如 ConcurrentLinkedQueue、ConcurrentSkipListMap。
特点:在多线程环境下提供更好的性能,避免传统锁的开销。
16. 协作式锁(Cooperative Locking)
通过非阻塞操作和信号的协作来实现线程同步,例如在 I/O 操作时,线程在等待时不持有锁。
特点:可用于减少阻塞,提升系统的响应性,但通常更复杂。
17. 偏好锁(Preferential Locking)
适用于让某些线程优先获取锁,以保证重要线程的高优先级,通常通过调度器和锁机制的结合来实现。
特点:适用于对响应时间有严格要求的应用。
18. 分段锁(Striped Locking)
一种分段锁机制,通过将数据分段并为每段加独立锁,降低锁冲突。
常用于 ConcurrentHashMap,有效分散锁的竞争压力。
特点:适合大量数据并发访问场景,减少锁的竞争。
19. 信号与事件锁
特点:适合复杂的线程协作或阶段性执行场景。
这些额外的锁机制扩展了 Java 的并发模型,使其能够在多种应用场景下优化并发性能。选择合适的锁类型取决于应用需求、线程访问模式和性能要求。
三、其他锁概念
在 Java 中,除了前面提到的各种锁之外,还存在一些特殊或实验性的锁机制和概念,这些主要用于高级或特定的场景。以下是一些少见但可以在并发编程中使用到的锁:
20. Read Mostly Lock
是一种针对读操作优化的锁机制。一般在读操作远多于写操作的场景中,提供了比传统读写锁更高效的读性能。
特点:通常通过将读操作分散在多个锁上来实现,从而减少锁的竞争。
21. 双重检查锁(Double-Checked Locking)
在懒加载单例模式中常用,通过两次检查是否需要加锁来避免不必要的同步。
特点:减小锁的范围,提升性能。但要注意使用 volatile 修饰实例变量以确保线程安全。
22. 自适应锁(Adaptive Locking)
一种动态调整锁策略的锁机制,根据锁的竞争程度调整为自旋锁或阻塞锁。
特点:适用于线程竞争程度不稳定的场景,可以在 JVM 内部优化锁的使用。
23. 手动锁膨胀和收缩(Manual Lock Inflation/Deflation)
在 Java 虚拟机中,锁在有竞争时会自动膨胀为重量级锁,但可以手动控制锁的膨胀和收缩以优化性能。
特点:多用于性能调优,适合非常高并发和高性能要求的场景。
24. 虚拟线程锁(Virtual Thread Locking)
Java 19 引入了虚拟线程(Project Loom),使得每个虚拟线程有自己的栈空间。虚拟线程的锁机制不同于普通线程,适用于高并发非阻塞场景。
特点:适合使用虚拟线程的高并发 I/O 操作,不会像传统线程一样受到阻塞的影响。
25. 基于硬件的锁(Hardware Lock Elision, HLE)
在一些高性能服务器或处理器上支持的锁机制,通过硬件级别实现锁省略(Elision)。
特点:非常高效,但依赖硬件支持,通常在 JVM 内部实现。
26. 轻量级并行锁(Lightweight Parallel Locks)
一种通过拆分资源来减少锁竞争的方法,使多个线程可以并行地访问不同的资源部分。
特点:适合资源可以分割的场景,如数据分块处理。
Java 的锁机制在不断进化,JVM 和并发包的更新也在尝试加入更多的并发控制方案。这些特殊的锁机制较为少见,通常在高性能、高并发或特定硬件环境中使用。对于一般应用场景,通常不需要使用到这些锁,但在性能调优或特殊需求时,可以考虑这些高级锁机制。
领取专属 10元无门槛券
私享最新 技术干货