当前位置: 首页 > news >正文

Lock 与 Synchronized:Java 并发控制的深度对比与实践

在 Java 并发编程中,线程同步是确保数据一致性和避免竞争条件的关键手段。synchronized 关键字和 java.util.concurrent.locks.Lock 接口是两种主流的同步工具,各有其设计理念和使用场景。作为 Java 开发者,理解两者的区别不仅有助于选择合适的工具,还能优化并发应用的性能和可维护性。本文将深入剖析 synchronizedLock 的原理、功能差异及适用场景,并通过 Java 代码展示两者的实际应用。


一、Synchronized 与 Lock 的基本概念

1. Synchronized

synchronized 是 Java 的内置关键字,用于实现线程同步。它基于 JVM 的监视器(Monitor)机制,通过对象锁确保同一时刻只有一个线程访问受保护的代码块或方法。

  • 使用方式
    • 同步代码块:锁定指定对象。
    • 同步方法:锁定当前对象(this)或类对象(Class)。
  • 语法示例
    synchronized (obj) {
        // 同步代码块
    }
    public synchronized void method() {
        // 同步方法
    }
    

2. Lock

Lock 是 Java 5 引入的 java.util.concurrent.locks 包中的接口,提供了更灵活的锁机制。常见的实现类包括 ReentrantLock,它通过显式加锁和解锁操作实现同步。

  • 使用方式
    • 手动调用 lock() 加锁,unlock() 解锁。
  • 语法示例
    Lock lock = new ReentrantLock();
    lock.lock();
    try {
        // 同步代码
    } finally {
        lock.unlock();
    }
    

二、Synchronized 与 Lock 的核心区别

1. 实现机制

  • Synchronized
    • 基于 JVM 内置的 Monitor 对象。
    • 使用对象头中的 Mark Word 存储锁状态。
    • 通过 monitorentermonitorexit 字节码指令实现。
  • Lock
    • 基于 Java 代码和 AQS(AbstractQueuedSynchronizer)框架。
    • 使用 volatile 变量和 CAS(Compare-And-Swap)管理锁状态。

2. 使用方式

  • Synchronized
    • 隐式加锁和解锁,JVM 自动管理。
    • 无需显式释放锁,异常时自动解锁。
  • Lock
    • 显式加锁和解锁,需手动调用 unlock()
    • 必须在 finally 块中释放锁,否则可能导致死锁。

3. 功能特性

特性SynchronizedLock
可重入性支持支持(ReentrantLock
公平性非公平可选公平(ReentrantLock(true)
中断性不支持支持(lockInterruptibly()
超时尝试不支持支持(tryLock(timeout)
条件变量单一 wait/notify多个 Condition 对象
锁状态查询不支持支持(isLocked()

4. 性能

  • Synchronized
    • JDK 1.6 后优化,引入偏向锁、轻量级锁和重量级锁,性能显著提升。
    • 在低竞争场景下开销小。
  • Lock
    • 基于 AQS,竞争激烈时性能优于重量级锁。
    • 高竞争下 CAS 操作可能导致自旋开销。

5. 异常处理

  • Synchronized:异常后自动释放锁。
  • Lock:异常后若未释放锁,可能导致死锁,需 finally 确保解锁。

三、底层原理剖析

1. Synchronized 的实现

synchronized 依赖 JVM 的 Monitor 机制:

  • 对象头:Mark Word 记录锁状态(无锁、偏向锁、轻量级锁、重量级锁)。
  • 锁升级
    1. 偏向锁:低竞争时偏向首个线程。
    2. 轻量级锁:少量竞争时使用 CAS。
    3. 重量级锁:高竞争时转为 Monitor。
  • 字节码
    public void method() {
        synchronized (this) {
            System.out.println("Hello");
        }
    }
    
    编译后:
    monitorenter
    getstatic java/lang/System.out
    ldc "Hello"
    invokevirtual java/io/PrintStream.println
    monitorexit
    

2. Lock 的实现

ReentrantLock 基于 AQS:

  • 状态变量volatile int state 表示锁状态(0 为未锁,>0 为已锁)。
  • 队列:AQS 维护等待线程的双向链表(CLH 变种)。
  • CAS:通过 compareAndSetState 原子更新状态。
  • 源码解析ReentrantLock.lock):
    public void lock() {
        sync.acquire(1);
    }
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            setState(c + acquires);
            return true;
        }
        return false;
    }
    

四、功能对比与使用场景

1. 可重入性

两者都支持可重入:

  • Synchronized
    public synchronized void outer() {
        inner();
    }
    public synchronized void inner() {
        System.out.println("Inner called");
    }
    
  • Lock
    Lock lock = new ReentrantLock();
    public void outer() {
        lock.lock();
        try {
            inner();
        } finally {
            lock.unlock();
        }
    }
    public void inner() {
        lock.lock();
        try {
            System.out.println("Inner called");
        } finally {
            lock.unlock();
        }
    }
    

2. 公平性

  • Synchronized:非公平,线程随机获取锁。
  • Lock:支持公平锁:
    Lock fairLock = new ReentrantLock(true); // 公平锁
    

3. 中断性与超时

  • Lock
    Lock lock = new ReentrantLock();
    if (lock.tryLock(1, TimeUnit.SECONDS)) {
        try {
            // 操作
        } finally {
            lock.unlock();
        }
    } else {
        System.out.println("Lock not acquired");
    }
    

4. 条件变量

  • Synchronized:单一 wait/notify
    synchronized (obj) {
        while (condition) {
            obj.wait();
        }
        obj.notify();
    }
    
  • Lock:多个 Condition
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    lock.lock();
    try {
        while (condition) {
            condition1.await();
        }
        condition2.signal();
    } finally {
        lock.unlock();
    }
    

五、Java 实践:对比 Synchronized 与 Lock

以下通过一个多线程计数器,展示两者的应用。

1. 环境准备

  • 依赖pom.xml):
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2. Synchronized 实现

@RestController
@RequestMapping("/sync")
public class SyncCounterController {
    private int counter = 0;
    private final Object lock = new Object();

    @GetMapping("/increment")
    public String increment() {
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (lock) {
                    counter++;
                }
            }
        }).start();
        return "Synchronized increment started";
    }

    @GetMapping("/value")
    public int getValue() {
        synchronized (lock) {
            return counter;
        }
    }
}

3. Lock 实现

@RestController
@RequestMapping("/lock")
public class LockCounterController {
    private int counter = 0;
    private final Lock lock = new ReentrantLock();

    @GetMapping("/increment")
    public String increment() {
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                lock.lock();
                try {
                    counter++;
                } finally {
                    lock.unlock();
                }
            }
        }).start();
        return "Lock increment started";
    }

    @GetMapping("/value")
    public int getValue() {
        lock.lock();
        try {
            return counter;
        } finally {
            lock.unlock();
        }
    }

    @GetMapping("/try-increment")
    public String tryIncrement() throws InterruptedException {
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                if (lock.tryLock()) {
                    try {
                        counter++;
                    } finally {
                        lock.unlock();
                    }
                }
            }
        }).start();
        return "Try-lock increment started";
    }
}

4. 主应用类

@SpringBootApplication
public class LockVsSyncApplication {
    public static void main(String[] args) {
        SpringApplication.run(LockVsSyncApplication.class, args);
    }
}

5. 测试

测试 1:基本同步
  • Synchronized
    • 请求:GET http://localhost:8080/sync/increment(10 次)
    • 查询:GET http://localhost:8080/sync/value
    • 结果:10000
  • Lock
    • 请求:GET http://localhost:8080/lock/increment(10 次)
    • 查询:GET http://localhost:8080/lock/value
    • 结果:10000
  • 分析:两者均正确同步,计数器无竞争问题。
测试 2:超时与尝试锁
  • Lock
    • 请求:GET http://localhost:8080/lock/try-increment(10 次)
    • 查询:GET http://localhost:8080/lock/value
    • 结果:可能小于 10000(因 tryLock 不阻塞)。
  • Synchronized:无类似功能。
  • 分析Lock 提供非阻塞选项,适合避免死锁。
测试 3:性能对比
  • 代码
    public class PerformanceTest {
        private static final int THREADS = 100;
        private static final int INCREMENTS = 10000;
    
        public static void main(String[] args) throws InterruptedException {
            testSynchronized();
            testLock();
        }
    
        static void testSynchronized() throws InterruptedException {
            Object lock = new Object();
            int[] counter = {0};
            Thread[] threads = new Thread[THREADS];
            long start = System.currentTimeMillis();
            for (int i = 0; i < THREADS; i++) {
                threads[i] = new Thread(() -> {
                    for (int j = 0; j < INCREMENTS; j++) {
                        synchronized (lock) {
                            counter[0]++;
                        }
                    }
                });
                threads[i].start();
            }
            for (Thread t : threads) t.join();
            long end = System.currentTimeMillis();
            System.out.println("Synchronized: " + (end - start) + "ms, Counter: " + counter[0]);
        }
    
        static void testLock() throws InterruptedException {
            Lock lock = new ReentrantLock();
            int[] counter = {0};
            Thread[] threads = new Thread[THREADS];
            long start = System.currentTimeMillis();
            for (int i = 0; i < THREADS; i++) {
                threads[i] = new Thread(() -> {
                    for (int j = 0; j < INCREMENTS; j++) {
                        lock.lock();
                        try {
                            counter[0]++;
                        } finally {
                            lock.unlock();
                        }
                    }
                });
                threads[i].start();
            }
            for (Thread t : threads) t.join();
            long end = System.currentTimeMillis();
            System.out.println("Lock: " + (end - start) + "ms, Counter: " + counter[0]);
        }
    }
    
  • 结果(8 核 CPU):
    Synchronized: 152ms, Counter: 1000000
    Lock: 168ms, Counter: 1000000
    
  • 分析:低竞争下 synchronized 略快,因 JVM 优化;高竞争下 Lock 可通过公平性调整性能。

六、使用场景与选择建议

1. Synchronized 适用场景

  • 简单同步需求:代码量少,无需复杂功能。
  • 低竞争环境:偏向锁和轻量级锁高效。
  • 异常安全要求高:无需手动释放锁。

2. Lock 适用场景

  • 复杂同步逻辑:需要条件变量、中断或超时。
  • 高竞争环境:公平锁或非阻塞尝试。
  • 动态控制:需查询锁状态或自定义行为。

3. 选择建议

  • 默认使用 Synchronized:简单场景,代码更简洁。
  • 优先考虑 Lock:需要高级功能或性能优化。

七、优化与实践经验

1. 性能优化

  • 减小锁范围
    synchronized (lock) {
        int temp = counter; // 读操作无需锁
        counter = temp + 1; // 仅写操作加锁
    }
    
  • 使用读写锁Lock):
    ReadWriteLock rwLock = new ReentrantReadWriteLock();
    rwLock.readLock().lock();
    

2. 异常处理

  • Lock
    lock.lock();
    try {
        // 操作
    } catch (Exception e) {
        log.error("Error occurred", e);
    } finally {
        lock.unlock();
    }
    

3. 调试技巧

  • jstack:检查锁状态和死锁。
  • VisualVM:监控线程和锁竞争。

八、总结

synchronizedLock 是 Java 并发控制的两种利器,前者简单高效,依赖 JVM 内置机制;后者功能丰富,基于 AQS 提供灵活性。本文从实现原理、功能对比到实践应用,全面展示了它们的区别。测试表明,synchronized 适合简单场景,而 Lock 在复杂需求中更具优势。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dtcms.com/a/127596.html

相关文章:

  • 什么是继承?js中有哪儿些继承?
  • 基于RISC-V内核的嵌入式系统在机器人关节控制中的应用研究
  • 1. CEF 下载及wrapper编译
  • 第四篇:Python文件操作与异常处理
  • PromptUp 网站介绍:AI助力,轻松创作
  • daz dForce 模拟后布料的变化不大,如何增强模拟的效果
  • 2025.04.12【生物信息学新工具】| SPADE:高效聚类与降维分析的利器
  • dify报错 Expecting value: line 1 column 1 (char 0)
  • 计算轴承|滚动轴承故障频率
  • 消失的它:揭开 CoreData 托管对象神秘的消失之谜(上)
  • 26考研——数据的表示和运算_运算方法和运算电路_定点数的加减运算 定点数的乘除运算(2)
  • 什么是原型、原型链?
  • Linux网络编程——数据链路层详解,以太网、MAC地址、MTU、ARP、DNS、NAT、代理服务器......
  • 屏幕模块解析
  • 数据分析-数据预处理
  • 【KWDB 创作者计划】第二卷:开发者实战篇
  • mysql 商城商品属性开发的动态解决方案
  • 个人博客系统后端 - 注册登录功能实现指南
  • 设计模式——工厂模式学习总结
  • 企业数据安全---数据分级
  • 【深度学习与大模型基础】第9章-条件概率以及条件概率的链式法则
  • Linux xorg-server 解析(二)- 如何调试 xorg-server
  • asm汇编字符串操作
  • 【NumPy科学计算:高性能数组操作核心指南】
  • a sort.py demo
  • 2024年React最新高频面试题及核心考点解析,涵盖基础、进阶和新特性,助你高效备战
  • Vue 接口请求 Nginx配置实时压缩 速度起飞
  • LVGL Arc控件和Roller控件详解
  • 【Java多线程】告别线程混乱!深度解析Java多线程4大实现方式(附实战案例)
  • Spring Boot 3.4.3 和 Spring Security 6.4.2 结合 JWT 实现用户登录