synchronized
的缺陷
Synchronized
在 Java 中是最基础的线程同步机制,尽管简单易用,但也存在一些缺陷和局限性:
-
性能开销:
synchronized
内部实现的监视器锁可能导致不必要的线程上下文切换和频繁竞争,从而引起性能下降,尤其是在高并发场景下。
-
不灵活:
- 一旦进入
synchronized
方法或代码块,就无法中断和提前退出,只能等执行完成。 - 不支持尝试加锁(try-lock),在尝试获取锁时无法设置超时。
- 一旦进入
-
持有锁的时间长:
- 由于
synchronized
的锁是隐式的,默认是持有锁直到方法执行结束,可能导致长时间阻塞其他线程,降低了系统的吞吐量。
- 由于
-
死锁风险:
- 在复杂的多线程环境中,如果使用不当,仍然可能导致死锁问题,尤其是在嵌套锁定或多个线程互相等待时。
-
条件通知:
synchronized
主要通过wait
和notify
方法来实现线程间协调,但这种方式可能较为繁琐和不直观。
Java Lock 的改进
Java 提供的 java.util.concurrent.locks
包中的 Lock
接口(通常使用 ReentrantLock
来实现)弥补了上述 synchronized
的缺陷,提供了更丰富和灵活的锁机制:
-
性能优化:
ReentrantLock
在高竞争情况下的性能通常优于synchronized
,其实现可以使用更高效的自旋锁机制,这样在短时间内获得锁的线程不会进行上下文切换。
-
可中断性:
ReentrantLock
提供了lockInterruptibly()
方法,允许线程在等待获取锁时能够响应中断,从而增加了线程的灵活性。
lock.lockInterruptibly();
-
超时尝试锁定:
- 通过
tryLock()
方法,线程可以尝试获取锁,如果锁被占用,可以选择继续等待或采取其他措施,从而避免在死锁或长时间等待时的无效阻塞。
if (lock.tryLock()) { try { // 执行需要同步的代码 } finally { lock.unlock(); } }
- 通过
-
锁的公平性:
ReentrantLock
可以配置为公平锁,这样当多个线程争夺锁时,会按照请求锁的顺序来处理,避免了“饥饿”情况。
Lock lock = new ReentrantLock(true); // 公平锁
-
条件变量支持:
ReentrantLock
提供了Condition
对象,可以实现更灵活的等待/通知机制,例如多个条件的通知,可以用await()
和signal()
方法来实现。
Condition condition = lock.newCondition(); condition.await(); condition.signal();
-
更大的控制范围:
- 由于提供了更加灵活的 API,开发者可以更好地控制锁的获取和释放时机,从而改善程序的结构和可读性。
总结
虽然 synchronized
提供了简单的互斥机制,但在高并发场景下存在性能和灵活性不足的缺陷。Java 的 Lock
接口及其实现(尤其是 ReentrantLock
)通过提供可中断、超时、公平性以及条件变量等功能,极大地增强了线程同步的灵活性和效率。因此,在需要高并发和复杂线程管理的场景中,Lock
是更推荐的选择。如果你有其他问题或需要更详细的解释,请随时在评论区留言探讨!