1 Java中锁的概念
锁用于控制多个线程对共享资源的访问。只有持有锁的线程才能访问被保护的资源,其他线程必须等待锁的释放。这种机制可以防止线程之间的竞争条件(Race Condition)。保证了同一时刻只有一个线程持有对象的锁并修改该对象,从而保障数据的安全。java中的锁包括
2 乐观锁和悲观锁
悲观锁
认为“冲突总是会发生”,在访问共享资源之前,线程会先获取锁,以确保只有当前线程能够访问资源,其他线程需要等待锁的释放。它适合竞争激烈、数据一致性要求高的场景,如数据库操作、银行转账等。在 Java 中,悲观锁的典型实现是通过 synchronized
或 ReentrantLock
。synchronized
是 Java 提供的内置锁,易于使用,但功能相对有限,主要用于方法或代码块的加锁保护。而 ReentrantLock
是显式锁,可以手动控制锁的获取和释放,并提供更高级的功能,如公平锁、非阻塞获取锁(tryLock()
)以及响应中断(lockInterruptibly()
)。由于悲观锁的加锁行为会导致线程阻塞,它可能带来性能开销,特别是在锁竞争激烈的场景中,可能会因为线程等待导致上下文切换频繁。
乐观锁
假设“冲突很少发生”,它通过无锁的方式来完成线程间的资源控制,并依赖版本号机制或 CAS(Compare-And-Swap,比较并交换)操作来实现。CAS 是一种硬件级别的原子操作,它会比较当前值是否符合预期值,如果符合则更新,不符合则重试。在 Java 中,AtomicInteger
、AtomicReference
等原子类就是基于 CAS 实现的。除了 CAS,乐观锁还可以通过版本号机制解决更复杂的业务场景,例如数据库更新中,读取某条记录的版本号,在提交时检查版本号是否与读取时一致,如果一致则更新,否则说明资源已被修改,需要重试或放弃。这种方式适合读多写少的场景,比如计数器累加、缓存刷新等,因为在这些场景中,数据的冲突发生概率低,使用乐观锁可以避免传统锁的阻塞问题,从而显著提高性能。
总体来看,悲观锁适合高冲突、数据一致性要求严格的场景,而乐观锁适合低冲突、高性能需求的场景。在实际应用中,悲观锁更容易实现且更安全,因为它完全避免了资源争用的问题,但需要付出一定的性能代价。而乐观锁则需要在实现中精心设计冲突处理逻辑,对于低冲突的场景能提供更高的吞吐量,但在高冲突场景下可能因为重试而导致性能下降。如果对性能要求较高且冲突概率低,可以选择乐观锁;而对数据一致性要求高时,应优先考虑悲观锁。
3 自旋锁(Spin Lock)
是一种轻量级的锁实现方式,其特点是线程在尝试获取锁时不会立即进入阻塞状态,而是通过循环不断尝试获取锁,直到成功为止。与传统的悲观锁不同,自旋锁假设线程会在较短的时间内释放锁,因此通过让线程“自旋”(占用 CPU 轮询)来等待锁的释放,而不是进入操作系统层面的阻塞状态。
自旋锁的核心思想是减少线程上下文切换的开销。在传统的悲观锁(如 synchronized
或 ReentrantLock
)中,如果线程未能获得锁,往往会进入等待队列并触发线程的挂起和唤醒操作,这些操作涉及操作系统的内核态转换,开销较高。而自旋锁通过线程不断尝试获取锁的方式,避免了这些系统调用的开销,适合锁的持有时间非常短的场景。
在 Java 中,自旋锁的实现通常基于 CAS(Compare-And-Swap)指令,例如 AtomicReference
或 Unsafe
类。以下是一个简单的自旋锁实现示例:
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
private final AtomicReference<Thread> owner = new AtomicReference<>();
public void lock() {
Thread currentThread = Thread.currentThread();
// 不断尝试获取锁
while (!owner.compareAndSet(null, currentThread)) {
// 自旋等待
}
}
public void unlock() {
Thread currentThread = Thread.currentThread();
// 释放锁
owner.compareAndSet(currentThread, null);
}
}
在上面的例子中,lock
方法使用 CAS 操作来尝试将锁的拥有者设置为当前线程,如果失败,则进入自旋。unlock
方法通过 CAS 将锁的拥有者重置为 null
。
自旋锁的优点是避免了线程上下文切换的开销,因此在锁的持有时间非常短、线程竞争不激烈的情况下性能较好,比如在多核 CPU 上短时间临界区的保护场景。但缺点也非常明显:自旋期间线程会占用 CPU 资源,因此当锁的持有时间较长或线程竞争较多时,自旋锁会导致大量的 CPU 资源浪费,反而降低系统性能。
在实际应用中,自旋锁往往配合一定的超时时间或重试次数来防止无限自旋。例如,尝试自旋若干次后仍未获得锁,则线程会选择进入阻塞状态,以避免长时间占用 CPU 资源。在 Java 的 ReentrantLock
实现中,自旋锁是其锁实现的一部分,用于优化轻量级的锁争用。此外,JDK 的 LockSupport
类也提供了类似的工具来帮助实现更复杂的自旋锁机制。
总结来说,自旋锁适用于锁持有时间短、竞争少的场景,如多线程计算中的局部变量保护。但对于锁竞争激烈或持有时间长的场景,自旋锁可能适得其反,应选择悲观锁或其他锁机制。
4 synchronized
synchronized
是 Java 提供的一种内置同步机制,用于解决多线程环境下的共享资源访问问题。它通过对象的**监视器锁(Monitor Lock)**来确保线程对共享资源的互斥访问,是最基础的线程同步手段。
4.1 synchronized 是什么?
synchronized
是 Java 的关键字,用于实现线程的互斥锁和同步机制。它可以修饰代码块、方法或类,保证同一时刻只有一个线程能够执行被 synchronized
修饰的代码,从而解决线程安全问题。
在底层,synchronized
是通过对象头中的监视器锁(Monitor)实现的。每个 Java 对象都有一个内置的锁,也称为对象锁。线程在进入 synchronized
代码块时会尝试获取该锁,获取成功后才能执行代码,执行完成后释放锁。其他线程在锁被占用时会进入等待状态。
4.2 synchronized 的用法
修饰实例方法
为实例方法加锁,锁定的是当前对象实例,确保同一时间只有一个线程可以调用该实例的同步方法。public class Counter { private int count = 0; public synchronized void increment() { count++; } }
如果两个线程访问同一个
Counter
对象的increment()
方法,会进行互斥控制。修饰静态方法
为静态方法加锁,锁定的是类对象(Class 对象),确保同一时间只有一个线程可以调用该类的同步静态方法。public class Counter { private static int count = 0; public static synchronized void increment() { count++; } }
静态方法锁的范围是全局的,不同实例调用相同的同步静态方法也会互斥。
修饰代码块
为指定的代码块加锁,可以更加灵活地控制锁的粒度,锁定的是synchronized
括号中的对象。public void increment() { synchronized (this) { count++; } }
使用代码块锁时,可以选择更细粒度的锁定范围,避免锁的范围过大影响性能。
4.3 synchronized 的底层原理
synchronized
的底层实现依赖于对象头中的Monitor。Java 对象头(Object Header)中包含了锁的相关信息(Mark Word)。其具体机制如下:
- 进入临界区:线程尝试获取 Monitor 锁,若获取成功,则进入临界区执行代码;否则进入等待状态。
- 退出临界区:线程释放 Monitor 锁,唤醒其他正在等待的线程。
- JVM 支持:Monitor 是由 JVM 实现的,具体依赖 CPU 指令中的 CAS(Compare-And-Swap)和锁状态标志位。
在 JDK 1.6 之后,为了优化 synchronized
的性能,JVM 引入了多种锁优化策略:
- 偏向锁:偏向于第一个获取锁的线程,减少加锁和解锁的开销。
- 轻量级锁:在锁竞争不激烈时,通过 CAS 实现轻量级锁,避免线程阻塞。
- 重量级锁:当线程竞争激烈时,升级为重量级锁,线程进入等待队列,阻塞等待锁释放。
4.4 synchronized 的特点
- 互斥性:同一时间只有一个线程能够执行被
synchronized
修饰的代码,其他线程必须等待锁的释放。 - 可重入性:一个线程可以多次获取同一个对象的锁,而不会发生死锁。这是因为每次锁的获取都会增加锁的计数器,释放锁时计数器递减。
public synchronized void methodA() { methodB(); // 同一线程可重入 } public synchronized void methodB() { // ... }
- 阻塞性:当一个线程占用锁时,其他线程会进入阻塞状态,等待锁的释放。
- 自动释放:线程退出
synchronized
代码块或方法时,会自动释放锁。 - 锁的范围:
- 实例锁:修饰实例方法或
synchronized(this)
锁定当前对象实例。 - 类锁:修饰静态方法或
synchronized(ClassName.class)
锁定类对象。
- 实例锁:修饰实例方法或
4.5 synchronized 的优缺点
优点:
- 简单易用:
synchronized
的语法简单,开发者无需关心锁的释放,易于维护。 - 安全可靠:内置锁由 JVM 实现,使用得当时不会发生死锁、锁泄露等问题。
- 性能优化:在 JDK 1.6 之后,通过偏向锁、轻量级锁等机制,提升了性能。
缺点:
- 阻塞开销:线程在等待锁时会阻塞,导致上下文切换开销较大。
- 全局影响:锁范围不合理可能会降低系统的并发性能。
- 无法尝试获取锁:与
ReentrantLock
不同,synchronized
不支持尝试加锁(如tryLock()
)或中断锁的获取。
4.6 使用 synchronized 注意事项
锁粒度要小:避免锁定过大的代码块,减少线程竞争的范围。
// 不推荐:锁住整个方法 public synchronized void process() { ... } // 推荐:仅锁住需要保护的部分 public void process() { synchronized (this) { // 临界区 } }
避免死锁:如果多个线程需要持有多个锁,务必确保锁的获取顺序一致。
偏向锁优化:对于没有多线程竞争的场景,可以尽量保持锁偏向,减少性能损耗。
5 ReentrantLock
ReentrantLock
是 Java 并发包 (java.util.concurrent.locks
) 中提供的一种显式锁(Explicit Lock),功能比 synchronized
更加丰富和灵活。它提供了可重入性、可中断性、超时锁获取等特性,是实现复杂同步需求的重要工具。
5.1 ReentrantLock 是什么?
ReentrantLock
是一个可重入锁,其作用和 synchronized
类似,都是用来解决多线程访问共享资源的线程安全问题。与 synchronized
不同的是,ReentrantLock
是显式的,需要手动加锁和释放锁,具有更强的灵活性。
可重入性是指一个线程可以多次获取同一把锁而不会导致死锁。ReentrantLock
在获取锁时会记录线程获取锁的次数,释放锁时则需要与获取锁次数匹配。
5.2 ReentrantLock 的基本用法
以下是 ReentrantLock
的基本使用方式:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保锁释放
}
}
}
关键点:
lock()
用于获取锁,线程在未获取到锁时会阻塞。unlock()
必须在try-finally
块中调用,确保锁在任何情况下都能被释放。
5.3 ReentrantLock 的主要特性
显式加锁与解锁
与synchronized
的隐式加锁方式不同,ReentrantLock
必须显式调用lock()
和unlock()
方法。虽然显式加锁略显繁琐,但提供了更多灵活性。可重入性
与synchronized
类似,ReentrantLock
是可重入的。同一线程可以多次获取锁,每次获取锁都会增加一个计数器,释放锁时计数器减 1,直到计数器归零,锁才被真正释放。public void methodA() { lock.lock(); try { methodB(); // 可重入调用 } finally { lock.unlock(); } } public void methodB() { lock.lock(); try { // 临界区代码 } finally { lock.unlock(); } }
公平锁与非公平锁
ReentrantLock
支持公平锁和非公平锁(默认是非公平锁)。- 公平锁:按照线程请求锁的顺序分配锁,先请求先获取,避免线程“饥饿”。
- 非公平锁:可能会插队,线程在尝试获取锁时直接竞争锁,提高吞吐量,但可能导致某些线程长期得不到锁。
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁 ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁
中断锁获取
使用lockInterruptibly()
方法,线程在尝试获取锁时可以响应中断。适合处理线程需要取消或超时的场景。try { lock.lockInterruptibly(); // 临界区代码 } catch (InterruptedException e) { // 响应中断 } finally { lock.unlock(); }
尝试获取锁
tryLock()
方法允许线程尝试获取锁,如果无法立即获取锁,则可以直接返回失败,而不会阻塞线程。if (lock.tryLock()) { try { // 获取锁成功,执行临界区代码 } finally { lock.unlock(); } } else { // 获取锁失败,执行其他操作 }
tryLock(long timeout, TimeUnit unit)
还支持超时等待锁的功能。if (lock.tryLock(5, TimeUnit.SECONDS)) { try { // 获取锁成功 } finally { lock.unlock(); } } else { // 超时未获取锁 }
条件变量(Condition)
ReentrantLock
提供了Condition
对象,用于实现线程间的等待/通知机制,类似于Object.wait()
和Object.notify()
,但更加灵活。
使用lock.newCondition()
方法创建Condition
对象,线程可以调用await()
方法等待,调用signal()
或signalAll()
方法唤醒等待线程。import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionExample { private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); public void waitForSignal() throws InterruptedException { lock.lock(); try { condition.await(); // 等待信号 } finally { lock.unlock(); } } public void sendSignal() { lock.lock(); try { condition.signal(); // 发送信号 } finally { lock.unlock(); } } }
5.4 ReentrantLock 的优缺点
优点:
- 更丰富的功能:支持公平锁、尝试锁、中断锁获取、条件变量等功能,灵活性远高于
synchronized
。 - 更精细的控制:显式加锁和解锁,能控制锁的范围和逻辑,更适合复杂场景。
- 高性能:在高并发场景下,通过非公平锁、减少线程阻塞切换的开销,相比
synchronized
性能更优。
缺点:
- 使用复杂:需要显式加锁和解锁,容易因为代码遗漏导致锁未释放。
- 易产生死锁:显式加锁机制要求开发者自行管理,若未小心处理容易出现死锁问题。
- 不如
synchronized
简洁:对于简单的同步需求,synchronized
更适合。
6 ReentrantLock 和 synchronized 的对比
特性 | ReentrantLock | synchronized |
---|---|---|
加锁方式 | 显式加锁,需要手动调用 lock() 和 unlock() |
隐式加锁,无需手动释放 |
是否支持条件变量 | 支持,通过 Condition 灵活实现 |
支持,但只能使用 wait() 和 notify() |
中断响应 | 支持,通过 lockInterruptibly() 响应中断 |
不支持 |
公平性 | 支持公平锁和非公平锁,默认非公平 | 不支持,锁是非公平的 |
性能优化 | 性能更高,适合高并发 | JDK 1.6 后性能也有优化 |
适用场景 | 适合复杂同步场景,特别是需要精细控制的场景 | 适合简单的同步需求 |
7 Semaphore 和 AtomicInteger
1 Semaphore
Semaphore
是 java.util.concurrent
包中的一个同步工具类,用于控制对共享资源的并发访问数量。它类似于一个计数器,可以通过许可证(permits)的机制限制访问线程的数量。它常用于实现流量控制、资源池管理等场景。在多线程环境下,如果多个线程需要访问有限数量的资源(如数据库连接、文件句柄),使用 Semaphore
可以有效限制并发线程的数量,避免资源耗尽或竞争问题。
2 Semaphore用法
Semaphore
通过 acquire()
和 release()
方法实现许可证的获取和释放。常见用法如下:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final Semaphore semaphore = new Semaphore(3); // 限制最多 3 个线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可证
System.out.println(Thread.currentThread().getName() + " 获取许可证");
Thread.sleep(2000); // 模拟任务执行
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可证
System.out.println(Thread.currentThread().getName() + " 释放许可证");
}
}).start();
}
}
}
- 许可数量:构造时指定许可证数量,
new Semaphore(int permits)
。 - 公平性:支持公平和非公平模式,默认非公平锁。
Semaphore fairSemaphore = new Semaphore(3, true); // 公平模式
- 阻塞和非阻塞:
acquire()
:阻塞获取许可证。tryAcquire()
:尝试获取许可证,失败时立即返回。
- 动态调整:可以通过
reducePermits(int reduction)
方法动态减少许可证数量。
3 Semaphore应用场景
- 资源池管理:限制线程访问资源池的数量,例如数据库连接池。
- 限流控制:对系统接口的并发访问量进行限制。
- 多线程任务分发:协调多线程执行任务的数量。
8 AtomicInteger
AtomicInteger
是 java.util.concurrent.atomic
包中的类,用于对整数值进行原子操作。它基于底层的 CAS(Compare-And-Swap)机制,实现了线程安全的整数增减操作,且性能高于传统的锁机制。在多线程环境下,使用普通的 int
类型操作(如 count++
)不是线程安全的,因为 count++
并非原子操作(包括读取、计算和写回三个步骤)。而 AtomicInteger
提供了线程安全的原子性操作,避免了锁的开销。
AtomicInteger
常见使用方式:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
private static final AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
int value = counter.incrementAndGet(); // 原子递增
System.out.println(Thread.currentThread().getName() + " 值:" + value);
}).start();
}
}
}
** AtomicInteger 的常用方法**
增减操作:
incrementAndGet()
:先递增再返回值。getAndIncrement()
:先返回值再递增。decrementAndGet()
和getAndDecrement()
:类似,用于递减。
设置和获取值:
get()
:获取当前值。set(int newValue)
:设置为指定值。lazySet(int newValue)
:最终会设置值,但可能有延迟。
比较并交换(CAS):
compareAndSet(int expect, int update)
:如果当前值等于expect
,则将值更新为update
。
加减指定值:
addAndGet(int delta)
:加上指定值后返回新值。getAndAdd(int delta)
:返回当前值后再加上指定值。
AtomicInteger 的特性
- 高性能:基于 CAS 操作,无需加锁,开销小。
- 线程安全:所有方法都保证了原子性,适合并发环境。
- 无阻塞算法:相比
synchronized
,性能更优。
AtomicInteger 的应用场景
- 计数器:用于统计并发环境中的操作次数。
- 唯一 ID 生成器:生成线程安全的唯一标识符。
- 限流控制:控制操作次数或速率。
9 读写锁
ReadWriteLock
是 java.util.concurrent.locks
包中的一个接口,用于实现多线程环境下的读写锁机制。读写锁通过区分读操作和写操作,允许多个线程同时读取共享资源,从而提高并发性;而对于写操作,则必须在没有其他线程进行读或写操作时才允许进行。这种机制有助于优化资源访问,特别是当读操作远多于写操作时,可以显著提升性能。
- 读锁 (Read Lock):允许多个线程同时获取读锁,当一个线程持有读锁时,其他线程仍然可以获取读锁,但是不能获取写锁。
- 写锁 (Write Lock):写锁是独占的,只有一个线程可以获取写锁,并且在写锁持有期间,其他线程不能获取读锁或写锁。
在并发编程中,读操作通常比写操作频繁,尤其是在共享资源读取多于写入的场景中。如果使用普通的互斥锁(例如 synchronized
或 ReentrantLock
),每次写操作时都会阻塞所有读操作,并且写操作本身也会受到其他写操作的阻塞。这会导致在读操作频繁的情况下,性能下降。
通过读写锁机制:
- 读锁:允许多个线程并行读取,提升读取操作的并发度。
- 写锁:保证写操作的独占性,确保数据一致性。
这种机制特别适用于“读多写少”的场景,例如缓存系统、数据库等。
1 读写锁的使用 (ReadWriteLock 接口)
Java 提供了 ReadWriteLock
接口以及其实现类 ReentrantReadWriteLock
。ReentrantReadWriteLock
是最常用的读写锁实现,它提供了读锁和写锁的功能。使用时,通过 readLock()
和 writeLock()
获取相应的锁。
示例代码:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private int sharedData = 0;
// 读操作
public int read() {
lock.readLock().lock(); // 获取读锁
try {
return sharedData; // 读取共享数据
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
// 写操作
public void write(int value) {
lock.writeLock().lock(); // 获取写锁
try {
sharedData = value; // 修改共享数据
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
// 启动多个读线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println("读线程: " + example.read());
}).start();
}
// 启动写线程
new Thread(() -> {
example.write(42);
System.out.println("写线程: 数据已更新");
}).start();
}
}
代码解释:
lock.readLock().lock()
:获取读锁,允许多个线程同时读取数据。lock.writeLock().lock()
:获取写锁,写锁是独占的,保证写操作期间没有其他线程进行读或写。
2 读写锁的特性
- 共享读锁:多个线程可以同时获取读锁,并行读取资源。
- 独占写锁:只有一个线程可以获取写锁,其他线程的读写操作会被阻塞,直到写操作完成。
- 优先级:Java 默认的
ReentrantReadWriteLock
是 非公平锁,即线程获取锁的顺序并不一定严格按照请求的顺序。如果需要保证公平性,可以通过构造函数传递true
来创建一个公平锁:ReadWriteLock fairLock = new ReentrantReadWriteLock(true); // 公平锁
典型使用场景:
- 缓存:多个线程频繁读取缓存数据,但缓存内容的更新较少。
- 数据库查询系统:多个线程可以并发执行读操作,但更新(写操作)需要互斥。
3 读写锁的性能
- 读锁并发性好:允许多个线程同时获取读锁,只要没有线程持有写锁。
- 写锁独占性强:写锁是独占的,能够保证数据的一致性,但会阻塞所有的读和写操作,性能可能会受到影响,特别是在写操作频繁的情况下。
- 公平性和非公平性:默认情况下,
ReentrantReadWriteLock
是非公平锁,在高并发场景下,非公平锁能够提供较好的性能。但如果需要避免线程饥饿,可以使用公平锁。
10 锁优化的几种方法
在并发编程中,锁优化是提高系统性能和减少锁竞争的重要手段。常见的锁优化方法包括锁的细化、锁的合并、锁的升级、使用非阻塞算法等。
1. 锁细化 (Lock Splitting)
锁细化是指将一个大范围的锁拆分成多个小范围的锁,每个小范围只控制特定的资源或操作,从而减少锁竞争。当一个锁保护了多个操作或多个资源时,可以考虑将它们分开,使每个操作或资源都有自己的锁。
2. 锁合并 (Lock Coarsening)
锁合并是将多个连续的锁操作合并为一个锁操作,以减少锁的频繁获取和释放,降低锁的开销。减少锁的申请和释放次数,降低上下文切换的成本,提升性能。当多个操作涉及同一个共享资源时,避免为每个操作单独加锁,而是将这些操作合并到一个临界区内,减少锁的获取和释放。
3. 锁升级 (Lock Upgrade)
锁升级是指在锁的获取过程中,从一个较低级别的锁(如读锁)逐步升级到一个更高级别的锁(如写锁)。例如,ReadWriteLock
中可以从读锁升级为写锁。例如,在数据库查询系统中,如果大多数操作是读取数据,可以先获取读锁,只有在需要修改数据时再升级为写锁。
4. 锁消除 (Lock Elision)
锁消除是指在编译或运行时,自动识别某些锁操作实际上不需要加锁,从而去除这些锁的开销。通常由 JVM 或编译器自动优化。如果某个共享资源的访问完全在单个线程中进行,JVM 可以优化掉这些不必要的同步。
5. 偏向锁 (Biased Locking)
偏向锁是一种优化机制,旨在减少无竞争情况下获取锁的开销。它会将锁偏向于第一个获取该锁的线程,避免后续线程的锁竞争。
在没有竞争的情况下,避免了锁的轻量级同步操作,从而减少了获取锁的开销。
6. 锁自由和无锁编程 (Lock-Free and Wait-Free)
锁自由和无锁编程是通过无锁算法(如 CAS 操作)来实现线程安全,而不依赖于传统的锁机制。这种方式避免了锁竞争和上下文切换,提升了并发性能。
- 锁自由:指在执行过程中,至少有一个线程能在有限的时间内完成操作。
- 无锁编程:指在执行过程中,每个线程都能在有限的时间内完成操作。