锁
锁
分类的角度很多
- 乐观锁/悲观锁
- 共享锁/独占锁
- 可重入锁/不可重入锁
乐观锁 vs 悲观锁
悲观锁:每次操作前都加锁
乐观锁:CAS 或版本号机制
一般来说,MySQL 中的行锁、表锁中,行锁会发生死锁,表锁不会
悲观锁通常用于多写场景,避免频繁失败和重试影响性能。
乐观锁通常用于多读场景,避免频繁加锁影响性能,大大提升了系统的吞吐量。
Lock vs sychronized
语法层面
- synchronized 是关键字,源码在 jvm 中,用 c++实现
- Lock 是接口,源码由 jdk 提供,用 java 语言实现
- 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁
synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
synchronized
是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized
关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。
ReentrantLock
是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
功能层面
二者均属于悲观锁,都具备基本的互斥、同步、锁重入功能
互斥:就是只有一个拿着锁
同步:
- synchronize:wait,notify
- Lock:利用条件变量提供的:await,signal
锁重入:是否可以对已经加上锁的对象加上第二道、第三道锁,到时候解锁也需要解多道
Lock 提供了许多 synchronized 不具备的功能,例如:
获取等待状态、公平锁、可打断、可超时、多条件变量
公平锁:先来先得(吞吐量不如非公平的高),非公平锁:可以插队
可打断、可超时:
Lock 有适合不同场景的实现,如 ReentrantLock,ReentrantReadWriteLock
ReentrantLock 比 synchronized 增加了一些高级功能
相比synchronized
,ReentrantLock
增加了一些高级功能。主要来说主要有三点:
等待可中断 :
ReentrantLock
提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()
来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。可实现公平锁 :
ReentrantLock
可以指定是公平锁还是非公平锁。而synchronized
只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock
默认情况是非公平的,可以通过ReentrantLock
类的ReentrantLock(boolean fair)
构造方法来制定是否是公平的。可实现选择性通知(锁可以绑定多个条件):
synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合可以实现等待/通知机制。ReentrantLock
类当然也可以实现,但是需要借助于Condition
接口与newCondition()
方法。典型如:阻塞队列 ArrayBlockingQueue 的 take 和 put 用的 notEmpty 和 notFull Condition 队列
如果你想使用上述功能,那么选择 ReentrantLock
是一个不错的选择。
性能层面
- 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不错
- 在竞争激烈时,Lock 的实现通常提供更好的性能