当前位置: 代码网 > it编程>编程语言>Java > Java中Synchronized与Lock锁机制的区别详解

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

2026年05月07日 Java 我要评论
在 java 并发编程中,synchronized 和 lock 是最常用的两种锁机制。它们都能保证线程安全,但实现原理、功能特性和性能表现上有显著差异。本文将带你彻底搞懂两者的区别,并通过流程图和对

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

一、引言

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

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

  • 语法与使用方式
  • 锁的获取与释放机制
  • 可中断性、超时尝试
  • 公平性选择
  • 底层实现(偏向锁、轻量级锁、重量级锁 vs aqs)
  • 性能表现(jdk 1.6 前后的变化)

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

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

特性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. 锁的可中断性

  • synchronized 线程在等待获取锁时不可被中断thread.interrupt() 无效,会一直阻塞)。
  • lock 提供了 lockinterruptibly(),允许等待锁的线程响应中断。
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. 公平性

  • synchronized 只支持非公平锁(等待队列中随机或按操作系统调度,可能线程饿死)。
  • reentrantlock 可选公平锁:new reentrantlock(true)。公平锁保证等待时间最长的线程先获得锁,但会降低吞吐量。
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 之后引入了偏向锁 → 轻量级锁 → 重量级锁的升级过程:

  • 偏向锁:锁总是被同一个线程获取,无需 cas。
  • 轻量级锁:多线程交替执行,使用 cas 尝试获取锁,不阻塞。
  • 重量级锁:竞争激烈,阻塞线程。

lock(reentrantlock)的 aqs 原理

reentrantlock 基于 aqs(abstractqueuedsynchronizer),内部维护一个 fifo 等待队列和一个 volatile int state 表示锁状态。通过 cas 设置状态,失败则加入等待队列并阻塞(locksupport.park())。

两者底层差异决定:

  • synchronized 的锁释放由 jvm 保证(异常或方法结束),不会遗漏。
  • lock 的灵活性更高,但手动释放容易出错。

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

synchronized 获取锁流程

lock(reentrantlock)获取锁流程

六、性能测试与选型建议

性能历史

  • jdk 1.5synchronized 性能很差,reentrantlock 明显更优。
  • jdk 1.6+synchronized 经过优化(偏向锁、轻量级锁、适应性自旋),低竞争场景下性能与 lock 相当甚至略好。
  • 高竞争场景:两者性能差别不大,但 lock 提供了更多控制(如公平锁、可中断)。

选择指南

场景推荐锁原因
简单同步代码块,无需高级特性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.“公平锁一定比非公平锁好”

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

九、总结与思维导图

最终建议

  • 优先使用 synchronized,除非遇到必须使用 lock 的场景(如超时、中断、多条件)。
  • 永远不要自己实现锁,使用 juc 包下的 lock 实现。
  • 手动 lock 时,unlock() 必须放在 finally 块中。

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

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com