Java锁相关知识与并发优化全总结

Java锁相关知识与并发优化全总结

在Java并发编程中,锁是解决线程安全问题的核心机制,用于控制多个线程对共享资源的访问,避免出现竞态条件、数据不一致等问题。从基础的synchronized锁,到基于AQS实现的ReentrantLock、CountDownLatch等,锁的实现不断优化,适配不同的并发场景。本文将系统梳理Java锁的基础知识、核心底层框架AQS原理、基于AQS实现的各类锁,以及锁在并发场景中的优化方案,为Java并发编程提供全面参考。

一、Java锁相关基础知识

Java中的锁本质是一种同步机制,通过限制多个线程对共享资源的并发访问,保证数据的原子性、可见性和有序性(Java内存模型JMM的三大特性)。理解锁的基础概念和分类,是掌握并发编程的前提。

1.1 锁的核心概念

  • 线程安全:多个线程并发访问共享资源时,不会出现数据错乱、不一致的情况,核心是保证原子性、可见性、有序性。

  • 原子性:一个操作或多个操作,要么全部执行且执行过程中不被中断,要么全部不执行(如i++并非原子操作,需锁或CAS保证)。

  • 可见性:一个线程修改共享变量后,其他线程能立即看到修改后的结果(如volatile关键字可保证可见性,锁也能间接保证)。

  • 有序性:线程执行顺序符合代码逻辑,避免指令重排序导致的并发问题(锁可禁止重排序,volatile可禁止部分重排序)。

竞态条件:多个线程同时操作共享资源,且操作结果依赖于线程执行顺序,导致最终结果不确定的现象,是锁需要解决的核心问题。

临界区:包含共享资源操作的代码块,锁的核心作用是保证临界区的互斥访问,同一时刻只有一个线程能进入临界区。

1.2 锁的分类(按核心维度)

1.2.1 按锁的互斥性分类

  • 独占锁(排他锁):同一时刻只有一个线程能持有锁,其他线程需等待锁释放后才能竞争(如synchronized、ReentrantLock)。

  • 共享锁:同一时刻多个线程可同时持有锁,适合读多写少的场景(如ReentrantReadWriteLock的读锁、Semaphore)。

1.2.2 按锁的获取机制分类

  • 乐观锁:假设没有线程竞争共享资源,先执行操作,执行完成后检查是否有冲突,若有冲突则重试(如CAS、版本号机制,无锁实现)。

  • 悲观锁:假设存在线程竞争,获取资源前先获取锁,确保只有自己能操作资源,避免冲突(如synchronized、ReentrantLock)。

1.2.3 按锁的可重入性分类

  • 可重入锁:同一线程可多次获取同一把锁,避免死锁(如synchronized、ReentrantLock,均为可重入锁)。例如:线程A获取锁后,在锁的临界区内再次调用需要同一把锁的方法,可直接获取锁,无需等待。

  • 不可重入锁:同一线程多次获取同一把锁时会阻塞,容易导致死锁(如早期的LockSupport实现的简单锁,实际开发中极少使用)。

1.2.4 按锁的底层实现分类

  • 内置锁(synchronized):JVM层面实现的锁,自动释放,使用简单,但灵活性差。

  • 显式锁(Lock接口):Java代码层面实现的锁,需手动获取和释放(try-finally保证),灵活性高,支持中断、超时、条件变量等特性(如ReentrantLock、ReentrantReadWriteLock)。

1.3 基础锁实现:synchronized详解

synchronized是Java最基础的内置锁,无需手动释放,由JVM自动管理,可用于修饰方法、代码块,保证临界区的互斥访问。

1.3.1 synchronized的使用场景

  • 修饰实例方法:锁对象是当前实例(this),同一时刻只有一个线程能执行该实例的该方法。

  • 修饰静态方法:锁对象是当前类的Class对象,同一时刻只有一个线程能执行该类的所有静态同步方法。

  • 修饰代码块:锁对象由开发者指定(如synchronized(obj)),灵活控制临界区范围,减少锁的粒度。

1.3.2 synchronized的底层实现(JDK1.8)

JDK1.6之前,synchronized是重量级锁,依赖操作系统的互斥量(Mutex)实现,切换线程时需切换内核态和用户态,性能较差;JDK1.6及以后,JVM对synchronized进行了优化,引入了偏向锁、轻量级锁、重量级锁的升级机制,提升性能。

  • 偏向锁:针对单线程访问场景,锁会偏向于第一个获取它的线程,后续该线程再次获取锁时,无需竞争,直接获取(通过对象头的偏向锁标记实现),减少锁竞争的开销。

  • 轻量级锁:当有两个线程竞争锁时,偏向锁升级为轻量级锁,线程通过CAS操作尝试获取锁,无需进入内核态,减少上下文切换开销。

  • 重量级锁:当多个线程竞争锁,且CAS操作失败时,轻量级锁升级为重量级锁,依赖操作系统的互斥量实现,此时线程会阻塞,等待锁释放(性能最差,但稳定性最高)。

核心原理:synchronized的锁对象(实例、Class对象)的对象头中,包含锁状态标记(偏向锁、轻量级锁、重量级锁),JVM通过修改对象头的锁状态,实现锁的获取和释放。

二、锁的底层实现框架AQS原理

AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发包(java.util.concurrent)中锁的核心底层框架,ReentrantLock、CountDownLatch、Semaphore等锁的实现,均基于AQS。AQS的核心思想是“模板方法模式”,定义了锁的获取、释放的核心流程,子类只需实现少量抽象方法,即可实现不同类型的锁。

2.1 AQS的核心结构

AQS的核心由两部分组成:状态变量同步队列,二者协同工作,实现锁的竞争与释放。

2.1.1 状态变量(state)

AQS内部维护一个volatile修饰的int类型变量state,用于表示锁的状态,不同类型的锁对state的定义不同:

  • 独占锁(如ReentrantLock):state=0表示锁未被持有,state>0表示锁被持有(state的值等于重入次数,可重入锁特性)。

  • 共享锁(如ReentrantReadWriteLock的读锁):state的高16位表示读锁的持有次数,低16位表示写锁的持有次数。

state的修改通过CAS操作保证原子性,AQS提供了getState()、setState()、compareAndSetState()三个方法,用于操作state变量。

2.1.2 同步队列(CLH队列)

当多个线程竞争锁失败时,会被封装为Node节点,加入到AQS的同步队列(CLH队列,一种双向链表队列)中,等待锁释放。同步队列的核心作用是“排队等待”,实现线程的有序竞争。

  • Node节点:每个Node节点包含线程引用、等待状态(如CANCELLED、SIGNAL等)、前驱节点(prev)、后继节点(next)。

  • 队列结构:头节点(head)是持有锁的线程节点,其他节点是等待锁的线程节点;当锁释放时,头节点会唤醒后继节点,让其尝试获取锁。

2.2 AQS的核心流程(模板方法)

AQS定义了锁的获取和释放的核心流程,子类通过实现抽象方法,适配不同的锁类型(独占锁、共享锁)。核心模板方法分为两类:独占式获取/释放锁、共享式获取/释放锁。

2.2.1 独占式获取锁(acquire(int arg))

独占式获取锁的核心流程(以ReentrantLock为例),适用于同一时刻只有一个线程能获取锁的场景:

  1. 调用tryAcquire(int arg)方法:子类实现,尝试获取锁(如ReentrantLock中,判断state是否为0,若为0则通过CAS修改state为1,获取锁;若state不为0,判断当前线程是否为持有锁的线程,若是则state加1,实现可重入)。

  2. 若tryAcquire成功,直接返回,线程获取锁并执行临界区代码。

  3. 若tryAcquire失败,调用addWaiter(Node.EXCLUSIVE)方法,将当前线程封装为独占式Node节点,加入同步队列尾部。

  4. 调用acquireQueued(Node node, int arg)方法,让节点在队列中自旋等待,直到获取锁;若等待过程中线程被中断,会记录中断状态,待获取锁后再处理中断。

2.2.2 独占式释放锁(release(int arg))

独占式释放锁的核心流程,用于释放锁并唤醒等待队列中的线程:

  1. 调用tryRelease(int arg)方法:子类实现,尝试释放锁(如ReentrantLock中,将state减1,若state减为0,则释放锁,返回true;若state不为0,返回false,表示还处于重入状态)。

  2. 若tryRelease成功,获取同步队列的头节点,调用unparkSuccessor(Node node)方法,唤醒头节点的后继节点,让其尝试获取锁。

  3. 释放锁完成,线程退出临界区。

2.2.3 共享式获取/释放锁

共享式获取/释放锁适用于多个线程可同时获取锁的场景(如ReentrantReadWriteLock的读锁),核心流程与独占式类似,但多了“共享计数”的逻辑:

  • 共享式获取锁(acquireShared(int arg)):调用tryAcquireShared(int arg)方法,尝试获取共享锁,若返回值>=0,表示获取成功;若返回值<0,表示获取失败,加入同步队列等待。

  • 共享式释放锁(releaseShared(int arg)):调用tryReleaseShared(int arg)方法,尝试释放共享锁,释放成功后,唤醒队列中所有等待的共享锁线程。

2.3 AQS的核心抽象方法(子类实现)

AQS定义了5个抽象方法,子类需根据锁的类型(独占/共享),实现对应的方法,其余流程由AQS的模板方法完成:

  • tryAcquire(int arg):独占式获取锁,返回true表示获取成功,false表示失败。

  • tryRelease(int arg):独占式释放锁,返回true表示释放成功,false表示未完全释放(如重入锁未释放完重入次数)。

  • tryAcquireShared(int arg):共享式获取锁,返回值>=0表示获取成功(返回值表示剩余可用的共享锁数量),<0表示失败。

  • tryReleaseShared(int arg):共享式释放锁,返回true表示释放成功,false表示未完全释放。

  • isHeldExclusively():判断当前线程是否独占式持有锁,用于可重入锁的判断。

2.4 AQS的ConditionObject(条件变量)

ConditionObject是AQS的内部类,实现了Condition接口,用于配合Lock锁实现线程间的协作(类似synchronized中的wait()、notify()方法),核心作用是让持有锁的线程暂时释放锁,进入等待状态,待条件满足后再被唤醒,重新获取锁。

2.4.1 ConditionObject的核心特性

  • 与Lock绑定:每个ConditionObject都与一个Lock锁(基于AQS实现)绑定,通过Lock的newCondition()方法创建,一个Lock可以创建多个ConditionObject,实现不同条件下的线程协作。

  • 等待队列:ConditionObject内部维护一个独立的等待队列(单向链表),与AQS的同步队列相互独立;调用await()方法的线程,会释放锁并加入该等待队列,等待被signal()/signalAll()唤醒。

  • 核心方法

    • await():当前线程释放锁,进入等待队列,直到被唤醒并重新获取锁;可响应中断。

    • await(long time, TimeUnit unit):带超时的等待,超时未被唤醒则自动返回,避免无限等待。

    • signal():唤醒等待队列中的一个线程,该线程会加入AQS的同步队列,尝试获取锁。

    • signalAll():唤醒等待队列中的所有线程,所有线程都会加入AQS的同步队列,竞争获取锁。

2.4.2 底层实现逻辑

ConditionObject的等待队列与AQS的同步队列协同工作,核心流程如下:

  1. 线程获取Lock锁(通过AQS的acquire方法)后,调用Condition的await()方法,会释放当前持有的锁(调用AQS的release方法),并将自身封装为Condition等待队列的节点,加入等待队列。

  2. 其他线程获取锁后,调用Condition的signal()方法,会将等待队列的头节点移出,加入AQS的同步队列,让其参与锁的竞争。

  3. 被唤醒的线程在AQS同步队列中自旋等待,获取到锁后,继续执行await()方法之后的代码。

注意:调用Condition的await()、signal()、signalAll()方法前,线程必须持有对应的Lock锁,否则会抛出IllegalMonitorStateException异常(与synchronized中wait()、notify()需在同步块中调用一致)。

三、基于AQS实现的其他锁

Java并发包中,大部分锁和同步工具类都基于AQS实现,它们通过实现AQS的抽象方法,适配不同的并发场景,以下是最常用的几种实现。

3.1 ReentrantLock(可重入独占锁)

ReentrantLock是基于AQS实现的显式独占锁,与synchronized功能类似,支持可重入特性,但灵活性更高,提供了中断、超时、公平锁/非公平锁等特性。

3.1.1 核心特性

  • 可重入:同一线程可多次获取锁,state记录重入次数,释放时需对应次数的释放。

  • 公平锁与非公平锁

    • 公平锁:线程获取锁的顺序与加入队列的顺序一致,避免线程饥饿,但性能略差(通过AQS的同步队列实现)。

    • 非公平锁:线程获取锁时,先尝试CAS获取锁,若失败再加入队列,可能导致先加入队列的线程后获取锁,但性能更好(默认是非公平锁)。

  • 中断支持:支持线程在等待锁时被中断(lockInterruptibly()方法),避免线程无限等待。

  • 超时获取:支持设置超时时间(tryLock(long timeout, TimeUnit unit)方法),若超时未获取到锁,返回false,避免死锁。

3.1.2 基于AQS的实现逻辑

  • state变量:表示锁的重入次数,state=0表示未持有锁,state>0表示持有锁,每次重入state加1,释放时state减1。

  • tryAcquire(int arg):非公平锁中,先尝试CAS修改state为1(未持有锁时),若成功则获取锁;若state不为0,判断当前线程是否为持有锁的线程,若是则state加1。公平锁中,需先判断队列中是否有前驱节点,若无再尝试CAS获取锁。

  • tryRelease(int arg):将state减1,若state减为0,返回true,表示完全释放锁;否则返回false。

3.2 ReentrantReadWriteLock(可重入读写锁)

ReentrantReadWriteLock是基于AQS实现的读写分离锁,支持多个线程同时读,同一时刻只有一个线程能写,适用于“读多写少”的并发场景,能提升并发性能(读操作无互斥,写操作与读、写操作互斥)。

3.2.1 核心特性

  • 读写分离:读锁(共享锁)和写锁(独占锁)分离,读锁可多个线程同时持有,写锁只能单个线程持有。

  • 可重入:读锁和写锁均支持可重入,读锁重入时,读锁计数加1;写锁重入时,写锁计数加1。

  • 互斥规则:写锁与读锁互斥、写锁与写锁互斥、读锁与读锁不互斥。

  • 降级支持:写锁可降级为读锁(先获取写锁,再获取读锁,最后释放写锁),但读锁不能升级为写锁(避免死锁)。

3.2.2 基于AQS的实现逻辑

ReentrantReadWriteLock内部维护两个锁:ReadLock(读锁)和WriteLock(写锁),共享同一个AQS实例,通过state变量的高16位和低16位分别记录读锁和写锁的持有次数:

  • state高16位:读锁持有次数(readCount),每次获取读锁,readCount加1;释放读锁,readCount减1。

  • state低16位:写锁持有次数(writeCount),每次获取写锁,writeCount加1;释放写锁,writeCount减1。

  • 写锁获取(tryAcquire):判断readCount是否为0且writeCount为0,若满足则通过CAS修改writeCount,获取写锁;若当前线程持有写锁,则writeCount加1(可重入)。

  • 读锁获取(tryAcquireShared):判断writeCount是否为0,若为0则通过CAS修改readCount,获取读锁;若当前线程持有写锁,则可直接获取读锁(写锁降级)。

3.3 CountDownLatch(倒计时锁存器)

CountDownLatch是基于AQS实现的同步工具类,用于等待多个线程完成任务后,主线程再继续执行,核心是“倒计时”机制,不可重复使用(倒计时到0后,无法重置)。

3.3.1 核心特性

  • 初始化时指定倒计时次数(count),count由AQS的state变量维护。

  • 多个线程调用countDown()方法,每次调用count减1(state减1)。

  • 主线程调用await()方法,会阻塞直到count减为0(state为0),然后继续执行。

3.3.2 基于AQS的实现逻辑

  • state变量:表示剩余的倒计时次数,初始化时state=count。

  • await()方法:调用AQS的acquireSharedInterruptibly(1),尝试获取共享锁;tryAcquireShared返回state是否为0,若为0则获取成功(不阻塞),若不为0则加入同步队列等待。

  • countDown()方法:调用AQS的releaseShared(1),tryReleaseShared将state减1,若state减为0,则唤醒队列中所有等待的线程。

3.4 Semaphore(信号量)

Semaphore是基于AQS实现的共享锁工具类,用于控制同时访问共享资源的线程数量,核心是“信号量计数”,可重复使用,适用于限流场景。

3.4.1 核心特性

  • 初始化时指定信号量数量(permits),permits由AQS的state变量维护,表示可同时访问的线程数量。

  • 线程调用acquire()方法,获取一个信号量(state减1),若state为0则阻塞,等待其他线程释放信号量。

  • 线程调用release()方法,释放一个信号量(state加1),唤醒队列中等待的线程。

  • 支持公平锁和非公平锁,默认是非公平锁。

3.4.2 基于AQS的实现逻辑

  • state变量:表示剩余的信号量数量,初始化时state=permits。

  • acquire()方法:调用AQS的acquireSharedInterruptibly(1),tryAcquireShared判断state是否大于0,若是则state减1,获取成功;若否则加入队列等待。

  • release()方法:调用AQS的releaseShared(1),tryReleaseShared将state加1,唤醒队列中等待的线程。

3.5 CyclicBarrier(循环屏障)

CyclicBarrier是基于AQS实现的同步工具类,与CountDownLatch类似,用于多个线程协同执行,但核心区别是:CyclicBarrier可重复使用(屏障触发后可重置),且所有线程必须到达屏障点后,才能继续执行,适用于“多个线程相互等待,同步执行某个任务”的场景(如多线程分阶段执行任务,所有线程完成第一阶段后,再一起进入第二阶段)。

3.5.1 核心特性

  • 可循环使用:当所有线程到达屏障点,触发屏障后,CyclicBarrier可通过reset()方法重置,再次使用(CountDownLatch不可重置)。

  • 屏障动作:可在构造方法中指定一个Runnable对象,当所有线程到达屏障点时,会先执行该Runnable对象(屏障动作),再继续执行各个线程的后续代码。

  • 初始化时指定“参与线程数”(parties),只有当参与的线程数全部到达屏障点(调用await()方法),屏障才会触发,所有线程继续执行。

  • 支持超时等待:await(long timeout, TimeUnit unit),超时未到达屏障点的线程会抛出TimeoutException,屏障会被打破,其他等待的线程也会被唤醒并抛出异常。

3.5.2 基于AQS的实现逻辑

CyclicBarrier内部通过AQS的ConditionObject实现线程等待与唤醒,核心依赖两个核心变量:参与线程数(parties)、当前等待线程数(count),其中count由AQS的state变量维护:

  • state变量:表示当前等待到达屏障点的线程数,初始化时state=parties,每次有线程调用await()方法,state减1。

  • await()方法:调用后,当前线程进入Condition的等待队列,等待其他线程到达;当state减为0时,触发屏障动作(若有),然后唤醒所有等待的线程,并重置state为parties(实现循环使用)。

  • reset()方法:将state重置为parties,同时唤醒所有等待的线程,中断当前的屏障等待,以便重新开始新一轮的同步。

3.6 锁的实操案例(贴合实际开发)

以下案例覆盖synchronized、ReentrantLock、ReentrantReadWriteLock、CountDownLatch、CyclicBarrier的实际使用场景,结合业务场景说明锁的选型与使用,帮助快速落地。

3.6.1 案例1:synchronized实操(简单并发安全控制)

场景:电商订单接口,多线程同时创建订单,需保证订单号唯一(简单计数器实现),低并发场景下使用synchronized,简洁高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 订单服务,使用synchronized保证计数器原子性
*/
public class OrderService {
// 订单计数器(共享资源)
private int orderCount = 0;

// 修饰实例方法,锁对象为this,保证多线程下计数器原子性
public synchronized String createOrder() {
orderCount++;
// 生成唯一订单号(简化)
return "ORDER_" + System.currentTimeMillis() + "_" + orderCount;
}

// 测试
public static void main(String[] args) throws InterruptedException {
OrderService orderService = new OrderService();
// 10个线程同时创建订单
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
String orderNo = orderService.createOrder();
System.out.println(Thread.currentThread().getName() + " 生成订单号:" + orderNo);
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
}
}

说明:低并发场景下,synchronized无需手动释放锁,代码简洁,可满足线程安全需求;若并发量提升,可替换为ReentrantLock或AtomicInteger。

3.6.2 案例2:ReentrantLock实操(高并发+超时+中断)

场景:高并发场景下的库存扣减,需保证库存操作原子性,支持超时获取锁,避免死锁,同时支持中断等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 库存服务,使用ReentrantLock保证高并发下库存安全
*/
public class StockService {
// 库存数量(共享资源)
private int stock = 100;
// 可重入锁(非公平锁,默认)
private final Lock lock = new ReentrantLock();

/**
* 扣减库存
* @param num 扣减数量
* @return 扣减是否成功
*/
public boolean deductStock(int num) {
try {
// 超时获取锁,3秒未获取到则返回失败,避免死锁
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
if (stock >= num) {
// 模拟库存扣减耗时
Thread.sleep(100);
stock -= num;
System.out.println(Thread.currentThread().getName() + " 扣减库存成功,剩余库存:" + stock);
return true;
} else {
System.out.println(Thread.currentThread().getName() + " 扣减库存失败,库存不足");
return false;
}
} finally {
// 手动释放锁,必须在finally中,避免锁泄漏
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 获取锁超时,扣减库存失败");
return false;
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 等待锁时被中断");
Thread.currentThread().interrupt(); // 恢复中断状态
return false;
}
}

// 测试
public static void main(String[] args) {
StockService stockService = new StockService();
ExecutorService executor = Executors.newFixedThreadPool(20);
for (int i = 0; i < 15; i++) {
executor.submit(() -> stockService.deductStock(10));
}
executor.shutdown();
}
}

说明:高并发场景下,ReentrantLock的非公平锁性能优于synchronized,tryLock超时机制可有效避免死锁,finally中释放锁可防止锁泄漏。

3.6.3 案例3:ReentrantReadWriteLock实操(读多写少场景)

场景:商品详情查询服务,读操作频繁(用户查询商品),写操作稀少(管理员更新商品),使用读写锁提升并发性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/**
* 商品服务,读多写少场景,使用读写锁优化性能
*/
public class GoodsService {
// 商品缓存(共享资源,key:商品ID,value:商品名称)
private final Map<Long, String> goodsCache = new HashMap<>();
// 读写锁
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读锁
private final Lock readLock = rwLock.readLock();
// 写锁
private final Lock writeLock = rwLock.writeLock();

/**
* 查询商品(读操作,共享锁)
*/
public String getGoodsName(Long goodsId) {
readLock.lock();
try {
// 模拟查询缓存耗时
Thread.sleep(50);
return goodsCache.getOrDefault(goodsId, "商品不存在");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "查询失败";
} finally {
readLock.unlock();
}
}

/**
* 更新商品(写操作,独占锁)
*/
public void updateGoods(Long goodsId, String goodsName) {
writeLock.lock();
try {
// 模拟更新缓存耗时
Thread.sleep(100);
goodsCache.put(goodsId, goodsName);
System.out.println(Thread.currentThread().getName() + " 更新商品成功:" + goodsId + "->" + goodsName);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
writeLock.unlock();
}
}

// 测试
public static void main(String[] args) {
GoodsService goodsService = new GoodsService();
ExecutorService executor = Executors.newFixedThreadPool(20);

// 1个线程更新商品(写操作)
executor.submit(() -> goodsService.updateGoods(1L, "苹果15"));

// 19个线程查询商品(读操作)
for (int i = 0; i < 19; i++) {
executor.submit(() -> {
String goodsName = goodsService.getGoodsName(1L);
System.out.println(Thread.currentThread().getName() + " 查询商品:" + goodsName);
});
}

executor.shutdown();
}
}

说明:读锁共享,多个读线程可同时执行,写锁独占,保证写操作的原子性;读多写少场景下,读写锁比独占锁性能提升显著。

3.6.4 案例4:CountDownLatch实操(多线程任务协同)

场景:主线程需要等待3个异步任务(查询用户、查询订单、查询商品)全部完成后,再汇总结果返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 多任务协同,使用CountDownLatch等待所有任务完成
*/
public class TaskCoordinator {
public static void main(String[] args) throws InterruptedException {
// 初始化倒计时器,指定3个任务
CountDownLatch countDownLatch = new CountDownLatch(3);
ExecutorService executor = Executors.newFixedThreadPool(3);

// 任务1:查询用户
executor.submit(() -> {
try {
Thread.sleep(1000);
System.out.println("任务1:查询用户完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 任务完成,倒计时减1
countDownLatch.countDown();
}
});

// 任务2:查询订单
executor.submit(() -> {
try {
Thread.sleep(1500);
System.out.println("任务2:查询订单完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
countDownLatch.countDown();
}
});

// 任务3:查询商品
executor.submit(() -> {
try {
Thread.sleep(800);
System.out.println("任务3:查询商品完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
countDownLatch.countDown();
}
});

// 主线程等待所有任务完成(阻塞)
System.out.println("主线程等待所有任务完成...");
countDownLatch.await();
System.out.println("所有任务完成,主线程汇总结果返回");

executor.shutdown();
}
}

说明:CountDownLatch不可重复使用,适合“一个线程等待多个线程完成任务”的场景,如主线程等待所有子任务完成后汇总结果。

3.6.5 案例5:CyclicBarrier实操(多线程循环协同)

场景:3个线程分两阶段执行任务,第一阶段所有线程完成后,执行屏障动作,再一起进入第二阶段,可循环执行多轮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 多线程循环协同,使用CyclicBarrier实现分阶段执行
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 初始化循环屏障,3个线程参与,指定屏障动作(所有线程到达后执行)
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
System.out.println("=== 屏障动作:所有线程完成当前阶段,准备进入下一阶段 ===");
});

ExecutorService executor = Executors.newFixedThreadPool(3);

// 3个线程,每个线程执行2轮任务
for (int i = 1; i <= 3; i++) {
int threadId = i;
executor.submit(() -> {
try {
// 第一轮任务
System.out.println("线程" + threadId + " 执行第一轮任务");
Thread.sleep(1000);
// 到达屏障点,等待其他线程
cyclicBarrier.await();

// 第二轮任务
System.out.println("线程" + threadId + " 执行第二轮任务");
Thread.sleep(800);
// 到达屏障点,等待其他线程
cyclicBarrier.await();

System.out.println("线程" + threadId + " 所有任务执行完成");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
});
}

executor.shutdown();
}
}

说明:CyclicBarrier可重复使用,适合“多个线程分阶段协同执行”的场景,屏障动作可用于阶段汇总、日志记录等操作。

四、锁在并发中的优化

锁的使用能解决线程安全问题,但不当的使用会导致性能瓶颈(如锁竞争激烈、锁粒度太大等)。锁的优化核心是“减少锁竞争、降低锁开销”,结合业务场景选择合适的锁和优化方案,平衡线程安全和并发性能。

4.1 锁优化的核心原则

  • 减少锁竞争:减少多个线程对同一把锁的竞争频率,降低阻塞时间。

  • 降低锁开销:减少锁的获取、释放过程中的性能消耗(如避免重量级锁的频繁使用)。

  • 合理控制锁粒度:锁粒度越小,并发度越高;但锁粒度太小会增加锁管理的开销,需平衡。

  • 结合场景选择锁:根据“读多写少”“写多读少”“高并发”等场景,选择合适的锁类型(如读写锁、信号量等)。

4.2 具体优化方案

4.2.1 锁粒度优化:从粗粒度锁到细粒度锁

粗粒度锁:一把锁控制整个共享资源(如修饰整个方法),并发度低,锁竞争激烈;细粒度锁:将共享资源拆分,每部分用单独的锁控制,减少锁竞争。

示例:HashMap线程不安全,ConcurrentHashMap通过“分段锁”(JDK1.7)或“CAS+ synchronized”(JDK1.8)实现细粒度锁,将数组分段,每段用单独的锁控制,多个线程可同时操作不同分段,提升并发性能。

4.2.2 锁消除:消除不必要的锁

JVM的即时编译(JIT)会对代码进行优化,若检测到某个锁保护的是局部变量(不会被多线程共享),则会自动消除该锁,减少锁开销。

示例:方法内部创建的StringBuffer(线程安全,自带锁),若该StringBuffer仅在方法内部使用,不会被多线程访问,JVM会消除其内部的锁,提升性能(相当于使用StringBuilder)。

4.2.3 锁粗化:合并频繁的锁操作

若一段代码中频繁获取和释放同一把锁(如循环中多次获取锁),会增加锁的开销,JVM会自动将这些锁操作合并为一次获取和释放,减少锁的切换次数。

示例:循环中多次调用synchronized修饰的方法,JVM会优化为在循环外获取一次锁,循环结束后释放锁,避免频繁的锁获取/释放。

4.2.4 选择合适的锁类型

  • 读多写少场景:使用ReentrantReadWriteLock,读锁共享,写锁独占,提升读操作的并发度。

  • 高并发、低延迟场景:使用ReentrantLock(非公平锁),性能优于synchronized(尤其在高并发下)。

  • 简单场景、低并发场景:使用synchronized,无需手动释放锁,代码简洁,维护成本低。

  • 限流场景:使用Semaphore,控制同时访问的线程数量,避免资源过载。

  • 多线程协作场景:使用CountDownLatch、CyclicBarrier等同步工具类,替代手动锁操作,提升代码可读性。

4.2.5 无锁优化:使用CAS替代锁

对于简单的原子操作(如计数器、状态更新),可使用CAS(Compare And Swap)操作替代锁,避免锁竞争的开销(CAS是乐观锁,无需阻塞线程)。

Java中提供了java.util.concurrent.atomic包,包含AtomicInteger、AtomicLong等原子类,内部基于CAS实现,可安全地实现原子操作,无需加锁。

4.2.6 避免死锁

死锁是锁使用中常见的问题,指多个线程互相持有对方需要的锁,导致无限阻塞。避免死锁的核心是“破坏死锁的四个必要条件”(互斥、持有并等待、不可剥夺、循环等待):

  • 按顺序获取锁:多个线程获取多把锁时,按固定的顺序获取(如先获取锁A,再获取锁B),避免循环等待。

  • 超时获取锁:使用ReentrantLock的tryLock(timeout)方法,若超时未获取到锁,释放已持有的锁,避免无限等待。

  • 减少锁的持有时间:尽量缩短锁的临界区范围,获取锁后尽快执行核心逻辑,释放锁,减少其他线程的等待时间。

五、总结

Java锁是并发编程的核心,从基础的synchronized内置锁,到基于AQS框架实现的各类显式锁,锁的实现不断优化,适配不同的并发场景。AQS作为锁的底层核心框架,通过状态变量和同步队列,统一了锁的获取和释放流程,其内部的ConditionObject实现了线程间的灵活协作,子类只需实现少量抽象方法,即可快速实现不同类型的锁。

基于AQS实现的锁和同步工具类(ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore、CyclicBarrier),覆盖了大部分并发场景,掌握其核心特性和使用场景,能大幅提升并发编程的效率和安全性。结合实操案例,可快速将锁的知识落地到实际开发中,避免线程安全问题。

在实际并发开发中,锁的优化是提升系统性能的关键:合理控制锁粒度、选择合适的锁类型、避免死锁、使用无锁方案(CAS)等,能在保证线程安全的前提下,最大化提升并发性能。同时,需结合业务场景,权衡锁的安全性和性能,避免过度优化(如锁粒度太小导致的管理开销),才能写出高效、安全的并发代码。

(注:文档部分内容可能由 AI 生成)

-------------本文结束感谢您的阅读-------------
Good for you!