Synchronized和ReentrantLock都是Java中用于实现线程同步的机制,它们各有优缺点和适用场景。以下是它们的比较和适用示例:
相似之处:
1.线程安全:两者都可以用于实现对临界区的保护,从而实现线程安全。
2.可重入性:两者都支持可重入性,允许一个线程多次获取同一把锁而不会发生死锁。
3.互斥锁:本质上都是互斥锁,确保在同一时刻只有一个线程能执行被同步的代码块。区别:
1.灵活性:
synchronized是Java语言的内置特性,使用简单,但功能有限。
ReentrantLock是一个类,提供了更高级的锁功能,例如:可中断的锁获取、超时获取锁、非阻塞尝试获取锁以及可实现更复杂的同步结构。
2.性能:
在较低竞争时,synchronized会自动使用优化,比如锁消除和锁粗化,使得它的性能在某些情况下可能高于ReentrantLock。
ReentrantLock可能在高竞争下表现更好,因为它可以提供非公平和公平锁模式,公平模式会严格按照请求锁的顺序来分配锁。
3.实现的功能:
ReentrantLock提供了更多控制功能,如lock()、unlock()方法,可在任何位置灵活调用。而synchronized在语法上是强制块结束时锁自动释放。
ReentrantLock提供tryLock()和lockInterruptibly()方法,以响应中断和超时。
4.条件变量:
ReentrantLock具有与之关联的Condition对象,可以搭配lock来更细粒度的控制线程通信。
synchronized配合Object的wait()和notify()/notifyAll()来进行线程之间的通信,但不如Condition灵活。适用场景:
synchronized:适用于简单的同步需求。由于其语法简单且嵌入在Java语言中,特别适合锁定范围与方法等价的情况。小规模、多线程竞争不高的情况下表现优异。适合开发者不想处理锁的复杂生命周期时使用。
ReentrantLock:适用于需要更高级的同步控制,或者锁定范围与方法不同时。特别是在需要公平锁、可中断锁操作、尝试获取带超时功能的锁,或者需要多个条件等待时,应选择ReentrantLock。当系统规模较大、线程数较多,且具有复杂同步需求的情境时表现突出。选择一个合适的锁机制,将有助于提升应用程序的性能并简化并发代码的编写。
- 补充
synchronized关键字本身
并没有直接使用AQS的state变量,它在JVM底层的实现与state所代表的同步状态概念是类似的:
记录锁的持有状态:JVM 在对象头中维护了一些信息来记录对象锁的状态,类似于 AQS 中的 state 变量。当线程第一次获取 synchronized 锁时,会标记该对象处于锁定状态;当线程重入锁时,会对锁的持有次数进行记录。
释放锁的判断:就像AQS中通过state减为0来判断锁是否完全释放一样,synchronized锁在JVM 底层也会根据锁的持有次数来判断是否可以释放锁。只有当线程所有的重入操作都完成,锁的持有次数降为0时,才会真正释放锁,允许其他线程获取该锁。