Java | ReentrantLock 锁和 synchronized 锁的区别和共同特点是什么?

时间:2024-11-01 18:28:13

ReentrantLocksynchronized 都是 Java 中的锁机制,主要用于实现线程间的互斥访问,确保线程安全。它们有一些共同点,也有各自的特性和区别。以下是二者的详细对比:

一、共同特点

  1. 可重入性:两者都是可重入锁,即同一线程可以多次获取该锁而不会被阻塞。可重入性允许递归调用带锁的方法而不会导致死锁。

  2. 互斥性:两者都提供了互斥访问机制,确保同一时间只有一个线程能够进入受保护的代码块。

  3. 线程安全性:两者都可以保证线程安全,确保共享资源在多线程环境下不出现数据不一致的情况。

  4. 内存可见性:两者在解锁之前,都会将工作内存中的修改刷新到主内存中,这样其他线程可以看到最新的共享变量值,保证了变量在多线程环境下的可见性。

二、区别

  1. 获取锁的方式

    • ReentrantLock:可以使用 lock() 方法手动获取锁,同时提供 tryLock()lockInterruptibly() 方法,可以更灵活地控制锁的获取,例如实现带超时的锁获取,响应中断等。
    • synchronized:自动获取锁,不支持手动控制和带超时的获取,一旦进入等待状态,只能等到锁被释放或线程被中断。
  2. 锁的释放

    • ReentrantLock:需要显式调用 unlock() 方法来释放锁,通常放在 try-finally 结构中,以确保锁的正确释放。
    • synchronized:锁的释放由 JVM 自动管理,当持有锁的线程退出同步代码块时,锁会自动释放。
  3. 公平锁和非公平锁

    • ReentrantLock:可以选择公平锁(严格按照请求顺序分配)或非公平锁(可能让新来的线程插队)。通过构造函数 new ReentrantLock(true) 来创建公平锁。
    • synchronized:始终是非公平锁,不保证线程获得锁的顺序。
  4. 性能

    • ReentrantLock:在高并发环境中表现良好,非公平锁模式下性能优于 synchronized。它允许更多的控制和优化。
    • synchronized:从 Java 6 起,synchronized 得到了大量优化,如偏向锁、轻量级锁和自适应自旋锁的引入,使得 synchronized 在低竞争环境下开销更小。
  5. 中断响应

    • ReentrantLock:支持线程在等待锁时响应中断(lockInterruptibly()),适用于需要灵活控制的场景。
    • synchronized:不支持中断等待,线程在等待锁时不能响应中断。
  6. Condition条件等待

    • ReentrantLock:可以结合 Condition 实现多条件等待和唤醒,类似于 Object.wait()Object.notify(),但更加灵活。可以在一个锁上创建多个 Condition 实例,实现不同的等待队列。
    • synchronized:依赖于 wait()notify()notifyAll() 实现线程间通信,且同一对象只能有一个等待队列,控制较为简单。
  7. 灵活性

    • ReentrantLock:提供更丰富的 API 和控制手段,例如支持超时获取锁、可以查询锁状态、实现多个条件等待等,适用于复杂的并发控制场景。
    • synchronized:语法简单,适合基本的互斥需求,但灵活性较低。

三、使用建议

  • ReentrantLock 适合需要复杂锁控制的场景,如实现公平锁、响应中断、带超时的锁、多个条件等待等高并发业务逻辑。
  • synchronized 适合简单的同步操作,并且由于其简单直接和 JVM 优化,在普通的线程互斥场景中推荐使用。

总结

特性 ReentrantLock synchronized
可重入性 支持 支持
互斥性 支持 支持
锁的释放 需要手动释放 (unlock()) 自动释放
公平锁 支持 不支持
性能 高并发环境性能较优 优化后在低并发环境性能较优
中断响应 支持 不支持
条件等待 Condition 可实现多个条件 wait()notify()notifyAll()
灵活性 高,适合复杂控制 低,适合基本互斥需求

选择哪种锁应基于具体业务需求。简单的同步建议使用 synchronized,而复杂的并发控制场景可以考虑 ReentrantLock