java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java Synchronized与Lock区别

Java中Synchronized与Lock锁机制的区别详解

作者:身如柳絮随风扬

在 Java 并发编程中,synchronized 和 Lock 是最常用的两种锁机制,它们都能保证线程安全,但实现原理、功能特性和性能表现上有显著差异,下面小编就和大家详细介绍一下吧

在 Java 并发编程中,synchronizedLock 是最常用的两种锁机制。它们都能保证线程安全,但实现原理、功能特性和性能表现上有显著差异。本文将带你彻底搞懂两者的区别,并通过流程图和对比表格助你做出正确的技术选型。

一、引言

线程安全是并发编程的核心问题。Java 提供了多种锁机制,其中 synchronized 是 JVM 内置的关键字,而 Lockjava.util.concurrent.locks 包下的接口(典型实现如 ReentrantLock)。许多开发者只知道“Lock 更灵活”,却不清楚具体灵活在哪里,以及什么场景该用哪个。

本文将围绕以下维度展开对比:

最后给出实战建议与流程图,帮助你直观理解两者在锁竞争时的行为差异。

二、一句话总结核心区别(先入为主)

特性synchronizedLock(以 ReentrantLock 为例)
实现层级JVM 关键字,内置语言特性Java 类库,基于 AQS 的 API
锁的获取方式隐式自动获取/释放显式调用 lock() / unlock()
可中断性不可中断(除非抛出异常)支持 lockInterruptibly()
超时获取锁不支持支持 tryLock(timeout, unit)
公平锁非公平锁(仅)可公平(构造参数)也可非公平
条件变量每个对象只有一个等待队列(wait/notify)一个 Lock 可绑定多个 Condition
是否可重入是(ReentrantLock 可实现)
锁释放方式自动(方法结束或异常)必须手动在 finally 中释放
锁状态监控无直接 API提供 tryLock()、isHeldByCurrentThread() 等
性能(低竞争)较好(JVM 优化,偏向锁)略差(对象创建开销)
性能(高竞争)早期较差,JDK 1.6 后改善稳定可控,吞吐量有时更高

三、深入对比:原理与代码示例

1. 语法与使用方式

synchronized:修饰实例方法、静态方法或代码块。

// 实例方法锁(当前实例)
public synchronized void method1() { /* 临界区 */ }

// 静态方法锁(Class 对象)
public static synchronized void method2() { /* 临界区 */ }

// 代码块锁(自定义对象)
public void method3() {
    synchronized (this) {
        // 临界区
    }
}

Lock:需要显式创建锁对象,并在 finally 块中释放。

Lock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();   // 必须手动释放,否则死锁
}

常见错误:忘记在 finallyunlock(),导致锁无法释放。这也是 synchronized 的“自动释放”优势所在。

2. 锁的可中断性

lock.lockInterruptibly();  // 等待时可被中断
try {
    // ...
} catch (InterruptedException e) {
    // 处理中断
} finally {
    lock.unlock();
}

3. 超时获取锁(避免死锁)

synchronized 无法尝试获取锁一段时间后放弃。LocktryLock(long time, TimeUnit unit) 能实现非阻塞尝试:

if (lock.tryLock(1, TimeUnit.SECONDS)) {
    try {
        // 获取成功
    } finally {
        lock.unlock();
    }
} else {
    // 未获取到锁,执行其他逻辑
}

4. 公平性

Lock fairLock = new ReentrantLock(true);   // 公平锁
Lock unfairLock = new ReentrantLock(false); // 非公平锁(默认)

5. 条件变量(Condition)

synchronized 通过 wait()notify() / notifyAll() 实现等待/通知,每个对象只有一个等待集。
Lock 可以创建多个 Condition 对象,实现更精细的线程唤醒。

ReentrantLock lock = new ReentrantLock();
Condition notFull  = lock.newCondition();
Condition notEmpty = lock.newCondition();

// 生产者
lock.lock();
try {
    while (队列满) notFull.await();
    // 生产
    notEmpty.signal();
} finally { lock.unlock(); }

四、底层实现原理简述

synchronized 的升级过程(JDK 1.6 优化)

在 JDK 1.6 之前,synchronized重量级锁(依赖操作系统的 mutex,用户态到内核态切换代价大)。1.6 之后引入了偏向锁 → 轻量级锁 → 重量级锁的升级过程:

Lock(ReentrantLock)的 AQS 原理

ReentrantLock 基于 AQS(AbstractQueuedSynchronizer),内部维护一个 FIFO 等待队列和一个 volatile int state 表示锁状态。通过 CAS 设置状态,失败则加入等待队列并阻塞(LockSupport.park())。

两者底层差异决定:

五、流程图对比:锁获取流程

synchronized 获取锁流程

Lock(ReentrantLock)获取锁流程

六、性能测试与选型建议

性能历史

选择指南

场景推荐锁原因
简单同步代码块,无需高级特性synchronized简洁、安全、自动释放
需要尝试获取锁并超时退出LocktryLock 超时机制
需要可中断的锁获取LocklockInterruptibly
需要多个条件变量(生产者-消费者)LockCondition 更灵活
需要公平锁Locksynchronized 不支持
性能要求极高且竞争极低synchronized偏向锁开销小
锁粗粒度,手动控制释放时机Lock可在不同方法间释放

七、实战代码对比:模拟售票系统

以下示例展示两种方式实现线程安全的余票扣除。

使用 synchronized

class TicketSync {
    private int tickets = 100;
    public synchronized boolean sell() {
        if (tickets <= 0) return false;
        tickets--;
        return true;
    }
}

使用 ReentrantLock

class TicketLock {
    private int tickets = 100;
    private final Lock lock = new ReentrantLock();
    public boolean sell() {
        lock.lock();
        try {
            if (tickets <= 0) return false;
            tickets--;
            return true;
        } finally {
            lock.unlock();
        }
    }
}

八、常见误区

1.“Lock 一定比 synchronized 快”

错。JDK 1.6 后,低竞争下 synchronized 有偏向锁优化,性能不差;高竞争下两者几乎持平。

2.“synchronized 不可重入”

错。synchronized可重入锁,同一个线程可以多次进入。

3.“Lock 必须要 try-finally,否则死锁”

对。若临界区抛出异常未释放锁,其他线程将永久等待。synchronized 由 JVM 保证释放。

4.“公平锁一定比非公平锁好”

错。公平锁减少了线程饿死,但上下文切换更频繁,吞吐量通常更低。

九、总结与思维导图

最终建议

到此这篇关于Java中Synchronized与Lock锁机制的区别详解的文章就介绍到这了,更多相关Java Synchronized与Lock区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文