java中锁的全面解析:类型、使用场景、优缺点及实现方式
在多线程编程中,锁是保证数据一致性和线程安全的核心机制。java 提供了丰富的锁机制来应对不同的并发场景。本文将从锁的基本概念出发,详细讲解 java 中常见的锁类型、它们的使用场景、优缺点以及底层实现原理,并通过代码示例帮助读者深入理解。
1. 锁的基本概念
锁是一种同步机制,用于控制多个线程对共享资源的访问。当一个线程获取锁后,其他线程必须等待该锁被释放才能继续执行,从而避免竞态条件(race condition)。
2. java 中常见的锁类型
2.1 互斥锁(mutex lock)
特点:
- 一次只允许一个线程持有锁。
- 保证临界区的独占访问。
常见实现:
synchronized关键字(内置锁)reentrantlock(可重入锁)
代码示例:使用 synchronized
public class counter {
private int count = 0;
// 同步方法,使用对象锁(this)
public synchronized void increment() {
count++;
}
// 同步代码块,使用指定对象锁
public void decrement() {
synchronized (this) {
count--;
}
}
public int getcount() {
return count;
}
}优点:
- 简单易用,无需手动释放锁。
- jvm 自动管理锁的获取与释放。
缺点:
- 无法中断等待中的线程。
- 不能设置超时时间。
- 只能是非公平锁(默认)。
2.2 可重入锁(reentrant lock)
特点:
- 支持同一个线程多次获取同一把锁(即“可重入”)。
- 提供更灵活的控制能力。
代码示例:使用 reentrantlock
import java.util.concurrent.locks.reentrantlock;
public class reentrantexample {
private final reentrantlock lock = new reentrantlock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
// 可以再次获取锁(可重入)
if (count == 1) {
system.out.println("thread " + thread.currentthread().getname() + " is re-entering the lock.");
}
} finally {
lock.unlock();
}
}
public int getcount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}优点:
- 支持可中断的锁获取(
lockinterruptibly())。 - 支持超时锁获取(
trylock(timeout))。 - 支持公平锁和非公平锁(通过构造函数选择)。
缺点:
- 必须手动释放锁,容易忘记
unlock()导致死锁。 - 语法比
synchronized复杂。
2.3 读写锁(readwritelock)
特点:
- 分离读操作和写操作的锁。
- 多个读线程可以同时访问共享资源,但写操作必须独占。
常见实现:reentrantreadwritelock
代码示例:读写锁的应用
import java.util.concurrent.locks.readwritelock;
import java.util.concurrent.locks.reentrantreadwritelock;
public class readwriteexample {
private final readwritelock lock = new reentrantreadwritelock();
private string data = "default";
// 读操作:多个线程可同时读
public string readdata() {
lock.readlock().lock();
try {
system.out.println(thread.currentthread().getname() + " is reading data: " + data);
return data;
} finally {
lock.readlock().unlock();
}
}
// 写操作:独占访问,其他读写均被阻塞
public void writedata(string newdata) {
lock.writelock().lock();
try {
system.out.println(thread.currentthread().getname() + " is writing data: " + newdata);
data = newdata;
} finally {
lock.writelock().unlock();
}
}
}优点:
- 读多写少的场景下性能显著提升(减少锁竞争)。
- 提高并发效率。
缺点:
- 写操作会阻塞所有读操作,可能导致“写饥饿”(writer starvation)。
- 逻辑复杂度增加。
2.4 原子锁(atomic lock)
特点:
- 使用原子操作(cas)实现无锁编程。
- 避免传统锁带来的上下文切换开销。
常见类:atomicinteger,atomicreference,stampedlock(分段锁)
代码示例:使用 atomicinteger
import java.util.concurrent.atomic.atomicinteger;
public class atomiccounter {
private final atomicinteger count = new atomicinteger(0);
public void increment() {
count.incrementandget(); // cas 操作,原子递增
}
public int getcount() {
return count.get();
}
}优点:
- 无锁机制,避免线程阻塞。
- 性能高,适合高并发场景。
缺点:
- 无法处理复杂的复合操作(如“读-修改-写”)。
- 存在 aba 问题(可通过
atomicstampedreference解决)。
2.5 stampedlock(戳记锁)
特点:
- jdk 8 引入,支持乐观读锁(optimistic read)。
- 读写分离,支持读写锁和乐观读锁三种模式。
代码示例:stampedlock 用法
import java.util.concurrent.locks.stampedlock;
public class stampedlockexample {
private final stampedlock stampedlock = new stampedlock();
private double x, y;
// 乐观读:不加锁,先尝试读取,失败则升级为悲观读
public double distancefromorigin() {
long stamp = stampedlock.tryoptimisticread();
double currentx = x, currenty = y;
// 检查是否在读期间发生了写操作(版本变化)
if (!stampedlock.validate(stamp)) {
stamp = stampedlock.readlock();
try {
currentx = x;
currenty = y;
} finally {
stampedlock.unlockread(stamp);
}
}
return math.sqrt(currentx * currentx + currenty * currenty);
}
// 写操作:独占锁
public void move(double deltax, double deltay) {
long stamp = stampedlock.writelock();
try {
x += deltax;
y += deltay;
} finally {
stampedlock.unlockwrite(stamp);
}
}
}优点:
- 读操作性能极高(乐观读无需阻塞)。
- 适用于读多写少且对读性能要求极高的场景。
缺点:
- api 复杂,需要显式验证版本。
- 容易出错(如忘记验证或释放锁)。
3. 锁的使用场景总结
| 锁类型 | 适用场景 | 推荐程度 |
|---|---|---|
synchronized | 简单同步,小范围临界区 | ⭐⭐⭐⭐⭐ |
reentrantlock | 需要超时、中断、公平性控制 | ⭐⭐⭐⭐☆ |
reentrantreadwritelock | 读多写少的共享数据 | ⭐⭐⭐⭐☆ |
atomicxxx | 简单计数器、状态标志 | ⭐⭐⭐⭐⭐ |
stampedlock | 极高读性能需求,读多写少 | ⭐⭐⭐☆☆ |
4. 锁的底层实现原理(简述)
synchronized:基于 jvm 的对象头(mark word)实现,通过 monitor 机制管理锁状态。reentrantlock:基于 aqs(abstractqueuedsynchronizer)实现,使用 cas+fifo 队列管理线程排队。stampedlock:基于版本戳(stamp)和状态位管理,支持乐观读。
5. 最佳实践建议
- 优先使用
synchronized,除非有特殊需求。 - 避免在
finally块外调用unlock(),防止死锁。 - 读写锁适用于读多写少的场景,避免“写饥饿”。
- 原子类适合简单操作,复杂逻辑仍需锁保护。
stampedlock适合高性能读场景,但需谨慎使用。
6. 结语
锁是 java 并发编程的核心,合理选择锁类型能极大提升程序性能与稳定性。掌握不同锁的特点与适用场景,是成为一名高级 java 工程师的必经之路。希望本文能为你提供清晰的指导!
到此这篇关于java中锁的全面解析之类型、使用场景、优缺点及实现方式(示例代码)的文章就介绍到这了,更多相关java锁使用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论