Java中的隐式锁(也称为内置锁或自动锁)是通过使用关键字实现的一种线程同步机制。当一个线程进入被synchronized修饰的方法或代码块时,它会自动获得对象级别的锁,退出该方法或代码块时则会自动释放这把锁。
在Java中,隐式锁的实现机制主要包括以下两种类型:
互斥锁(Mutex)
虽然Java标准库并未直接暴露操作系统的互斥锁提供使用,但在Java虚拟机对synchronized关键字处理的底层实现中,当锁竞争激烈且必须升级为重量级锁时,会利用操作系统的互斥量机制来确保在同一时刻仅允许一个线程持有锁,从而实现严格的线程互斥控制。
内部锁(Intrinsinc Lock)或监视器锁(Monitor Lock)
Java语言为每个对象内建了一个监视器锁,这是一个更高级别的抽象。我们可以通过使用synchronized关键字即可便捷地管理和操作这些锁。当一个线程访问被synchronized修饰的方法或代码块时,会自动获取相应对象的监视器锁,并在执行完毕后自动释放,这一过程对用户透明,故被称为隐式锁。每个Java对象均与一个监视器锁关联,以此来协调对该对象状态访问的并发控制。
- 实例方法同步:锁是当前实例对象。
- 静态方法同步:锁是当前类的Class对象。
- 同步代码块(使用对象实例作为锁)。
- 同步代码块(使用类的Class对象作为锁)
public class BankAccount {
private double balance; // 共享资源
// 隐式锁保护存款操作(锁为当前对象实例)
public synchronized void deposit(double amount) {
balance += amount;
System.out.println("存款: +" + amount + " | 余额: " + balance);
}
// 隐式锁保护取款操作
public synchronized void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
System.out.println("取款: -" + amount + " | 余额: " + balance);
} else {
System.out.println("取款失败!余额不足");
}
}
public static void main(String[] args) {
BankAccount account = new BankAccount();
// 多线程并发操作账户
new Thread(() -> account.deposit(100)).start();
new Thread(() -> account.withdraw(50)).start();
new Thread(() -> account.withdraw(70)).start();
}
}
输出结果:
存款: +100.0 | 余额: 100.0
取款: -50.0 | 余额: 50.0
取款失败!余额不足
说明:
synchronized
方法保证同一时间只有一个线程能操作balance
。public class IdGenerator {
private static int counter = 0;
// 静态方法锁(锁为Class对象)
public static synchronized int generateId() {
return ++counter;
}
public static void main(String[] args) {
// 多线程调用静态方法
for (int i = 0; i < 5; i++) {
new Thread(() -> {
int id = IdGenerator.generateId();
System.out.println(Thread.currentThread().getName() + " : " + id);
}).start();
}
}
}
输出结果:顺序可能不同,但ID不重复
Thread-0 : 1
Thread-2 : 3
Thread-1 : 2
...
说明:
counter
。IdGenerator.class
,所有实例共享同一把锁。对于静态方法锁(类级别锁)典型应用场景全局唯一ID生成器,单例模式实现(线程安全版),全局配置管理,跨实例的共享资源控制
public class OrderIdGenerator {
private static int counter = 0;
// 类级别锁保护
public static synchronized String generateId() {
return "ORD-" + System.currentTimeMillis() + "-" + (++counter);
}
}
// 多线程安全调用
IntStream.range(0, 10).forEach(i ->
new Thread(() -> {
System.out.println(OrderIdGenerator.generateId());
}).start()
);
public class DatabasePool {
private static volatile DatabasePool instance;
// 私有构造方法
private DatabasePool() { /* 初始化连接池 */ }
// 类锁保护实例创建
public static DatabasePool getInstance() {
if (instance == null) {
synchronized (DatabasePool.class) { // 类级别锁
if (instance == null) {
instance = new DatabasePool();
}
}
}
return instance;
}
}
public class AppConfig {
private static Properties config;
// 类锁保护配置加载
public static synchronized void reloadConfig() {
Properties newConfig = new Properties();
try (InputStream is = new FileInputStream("config.properties")) {
newConfig.load(is);
config = newConfig; // 原子性更新
}
}
// 读操作无需同步(final字段可见性保证)
public static String get(String key) {
return config.getProperty(key);
}
}
public class RateLimiter {
private static int requestCount = 0;
private static final int MAX_REQUESTS = 100;
// 类锁保护全局计数器
public static synchronized boolean allowRequest() {
if (requestCount >= MAX_REQUESTS) {
return false;
}
requestCount++;
return true;
}
public static synchronized void releaseRequest() {
requestCount--;
}
}
public class Cache {
private final Object lock = new Object(); // 专用锁对象
private Map data = new HashMap<>();
public void update(String key, String value) {
// 非同步操作(无竞争)
System.out.println("预处理...");
// 仅同步共享资源操作
synchronized (lock) {
data.put(key, value);
System.out.println("更新: " + key + "=" + value);
}
}
public String get(String key) {
synchronized (lock) {
return data.get(key);
}
}
}
对于同步代码块(减小细粒度)典型应用场景,缓存系统(读写分离),订单状态变更(局部同步),连接池管理(减少锁范围),批量处理(分段加锁)
public class CacheSystem {
private final Map cache = new HashMap<>();
private final Object lock = new Object(); // 专用锁对象
// 读操作(不加锁)
public String get(String key) {
return cache.get(key);
}
// 写操作(仅同步必要部分)
public void update(String key, String value) {
// 非同步预处理(如参数校验)
if (key == null) throw new IllegalArgumentException();
synchronized (lock) { // 只锁核心逻辑
cache.put(key, value);
System.out.println("Updated: " + key);
}
}
}
public class OrderService {
private Order currentOrder;
public void processOrder(Order newOrder) {
// 非关键操作(如日志)
log("Processing order: " + newOrder.getId());
// 仅同步状态变更
synchronized (this) {
if (currentOrder == null) {
currentOrder = newOrder;
updateDatabase(newOrder); // 关键操作
}
}
}
private void log(String message) { /* 非同步操作 */ }
}
getConnection()
方法会导致连接使用期间也持有锁public class ConnectionPool {
private final LinkedList pool = new LinkedList<>();
private final Object poolLock = new Object();
public Connection getConnection() throws SQLException {
Connection conn;
synchronized (poolLock) { // 仅同步分配过程
if (pool.isEmpty()) {
conn = createNewConnection();
} else {
conn = pool.removeFirst();
}
}
// 连接初始化(非同步操作)
conn.setAutoCommit(true);
return conn;
}
}
public class BatchProcessor {
private final List
隐式锁适用于相对简单的多线程同步需求,尤其是在只需要保护某个对象状态完整性,且无需过多关注锁策略灵活性的场合。
显式锁是由java.util.concurrent.locks.Lock接口及其诸多实现类提供的同步机制,相较于通过synchronized关键字实现的隐式锁机制,显式锁赋予开发者更为精细和灵活的控制能力,使其能够在多线程环境中精准掌控同步动作。显式锁的核心作用在于确保在任何时刻仅有一个线程能够访问关键代码段或共享数据,从而有效防止数据不一致性问题和竞态条件。
相较于隐式锁,显式锁提供了更为多样化的锁操作选项,包括但不限于支持线程在等待锁时可被中断、根据先后顺序分配锁资源的公平锁与非公平锁机制,以及能够设定锁获取等待时间的定时锁功能。这些特性共同增强了显式锁在面对复杂并发场景时的适应性和可调控性,使之成为解决高度定制化同步需求的理想工具。
日常开发中,常见的显式锁分类有如下几种:
ReentrantLock:可重入锁
可重入锁,继承自Lock接口,支持可中断锁、公平锁和非公平锁的选择。可重入意味着同一个线程可以多次获取同一线程持有的锁。
ReentrantReadWriteLock:读写锁
读写锁,提供了两个锁,一个是读锁,允许多个线程同时读取;另一个是写锁,同一时间内只允许一个线程写入,写锁会排斥所有读锁和写锁。
带版本戳的锁,提供了乐观读、悲观读写模式,适合于读多写少的场景,可以提升系统性能。
- ReentrantLock:可重入锁(银行账户转账场景)
- ReentrantReadWriteLock:读写锁(商品库存缓存)
- StampedLock:乐观读锁(实时股票报价系统)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class BankAccount {
private final Lock lock = new ReentrantLock(); // 默认非公平锁
private int balance;
public BankAccount(int balance) {
this.balance = balance;
}
// 转账操作(支持超时与中断)
public boolean transfer(BankAccount to, int amount) throws InterruptedException {
long startTime = System.currentTimeMillis();
while (true) {
if (this.lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试获取转出账户锁
try {
if (to.lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试获取转入账户锁
try {
if (balance >= amount) {
balance -= amount;
to.balance += amount;
return true;
}
return false; // 余额不足
} finally {
to.lock.unlock();
}
}
} finally {
this.lock.unlock();
}
}
// 总等待超时判定
if (System.currentTimeMillis() - startTime > 3000) {
System.out.println(Thread.currentThread().getName() + ": 转账超时放弃");
return false;
}
Thread.sleep(100); // 避免CPU忙等
}
}
}
// 使用示例
public class TransferDemo {
public static void main(String[] args) {
BankAccount acc1 = new BankAccount(1000);
BankAccount acc2 = new BankAccount(500);
new Thread(() -> {
try {
acc1.transfer(acc2, 200); // 线程1: acc1 -> acc2转200
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread-1").start();
new Thread(() -> {
try {
acc2.transfer(acc1, 100); // 线程2: acc2 -> acc1转100
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread-2").start();
}
}
import java.util.concurrent.locks.*;
class ProductInventory {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true); // 公平锁
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private int stock = 100; // 初始库存
// 读操作:多个线程可同时执行
public int getStock() {
readLock.lock();
try {
return stock;
} finally {
readLock.unlock();
}
}
// 写操作:独占访问
public void updateStock(int newStock) {
writeLock.lock();
try {
stock = newStock;
} finally {
writeLock.unlock();
}
}
}
// 使用示例
public class InventoryDemo {
public static void main(String[] args) {
ProductInventory inventory = new ProductInventory();
// 10个读线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()
+ " 读取库存: " + inventory.getStock());
}, "Reader-" + i).start();
}
// 1个写线程(更新库存)
new Thread(() -> {
inventory.updateStock(80);
System.out.println("库存更新为: 80");
}, "Writer").start();
}
}
import java.util.concurrent.locks.StampedLock;
class StockQuote {
private final StampedLock sl = new StampedLock();
private double price = 100.0; // 初始价格
// 乐观读:无锁快照读取
public double getCurrentPrice() {
long stamp = sl.tryOptimisticRead(); // 获取乐观读戳
double currentPrice = this.price; // 读取共享变量
// 检查期间是否有写操作发生
if (!sl.validate(stamp)) {
stamp = sl.readLock(); // 升级为悲观读锁
try {
currentPrice = price;
} finally {
sl.unlockRead(stamp);
}
}
return currentPrice;
}
// 写操作:独占访问
public void updatePrice(double newPrice) {
long stamp = sl.writeLock();
try {
price = newPrice;
} finally {
sl.unlockWrite(stamp);
}
}
}
// 使用示例
public class StockQuoteDemo {
public static void main(String[] args) {
StockQuote quote = new StockQuote();
// 读线程(高频)
for (int i = 0; i < 20; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()
+ " 报价: " + quote.getCurrentPrice());
}, "Reader-" + i).start();
}
// 写线程(低频)
new Thread(() -> {
quote.updatePrice(105.5);
System.out.println("价格更新为: 105.5");
}, "Writer").start();
}
}
synchronized
但要求更多功能:
lockInterruptibly()
方法可以中断等待,避免线程无限期阻塞。这在synchronized
中无法实现。tryLock()
方法。如果锁当前不可用,线程可以立即返回去做其他事情(例如尝试获取其他资源、记录日志、提供备选方案),而不是傻等。场景示例: 避免死锁(尝试按特定顺序获取多个锁,失败则释放已获得的锁并重试或放弃)。tryLock(long time, TimeUnit unit)
方法。线程只愿意等待锁一定的时间,超时后放弃获取锁。场景示例: 访问需要保证响应时间的资源(如数据库连接池、外部服务调用),防止线程因锁争用导致整体系统响应变慢。 new ReentrantLock(true)
创建公平锁,保证等待时间最长的线程优先获得锁。场景示例: 对线程获取锁的顺序有严格要求,避免线程饥饿(如某些低优先级线程永远拿不到锁)。注意:公平锁通常性能低于非公平锁。 Lock
接口明确要求lock()
和unlock()
必须成对出现,且unlock()
通常放在finally
块中确保释放。这种显式控制比synchronized
块更灵活,尤其在跨越多个方法或需要根据复杂条件释放锁时。
synchronized
或ReentrantLock
),读写锁在读取密集型场景下能显著提高并发性能。在 “读非常多,写非常少” 且对读性能要求极高的场景下,它是
ReentrantReadWriteLock
的一个更高级、更激进的替代方案。它引入了乐观读模式。
tryOptimisticRead()
进行乐观读。
validate(stamp)
检查在读取过程中是否有写操作发生。validate
返回true
(没有写操作干扰),则乐观读成功,读取的数据有效。validate
返回false
(有写操作发生),则升级为标准的悲观读锁 (readLock()
) 或写锁 (writeLock()
),然后重新读取数据以确保一致性锁类型 | 核心特性 | 最佳适用场景 | 关键优势 | 注意事项 |
ReentrantLock | 可重入、可中断、可超时、可公平 | 需要比synchronized 更精细控制的同步;需要尝试获取锁或超时;需要公平性 |
功能丰富,控制灵活 | 必须手动unlock() ;公平锁性能稍差 |
ReentrantReadWriteLock | 读锁共享、写锁独占 | 读多写少的共享数据访问(缓存、资源状态查询) | 显著提升读取密集型场景的并发性能 | 注意锁升级问题(持有读锁时不能直接获取写锁) |
StampedLock | 乐观读、悲观读、写锁;带版本戳 | 读非常多,写极少且对读性能要求极端高 | 乐观读模式提供接近无锁的读性能,大幅提升吞吐量 | 不可重入;API更复杂;乐观读需验证;无Condition |
synchronized
: 如果简单的互斥能满足需求,且不需要可中断、超时、尝试获取、公平性等高级特性,synchronized
通常是更简洁、更不容易出错的选择(JVM内部优化也在持续改进)。ReentrantLock
: 当需要可中断、超时、尝试获取、公平性控制,或者锁的获取/释放逻辑跨越复杂代码块(不能简单地用synchronized
块包裹)时。StampedLock
: 当读操作是性能的绝对关键路径,写操作极其罕见,并且愿意承担更复杂的API和潜在的重试开销来换取最高读取吞吐量时。评估乐观读失败的概率和重试成本非常重要。