synchronized 和 ReentrantLock 之间的区别
synchronized特性
对于 synchronized
来说,它本身就是:
- 乐观锁 或 悲观锁(自适应)
- 轻量级锁 或 重量级锁(自适应)
- 自旋锁 或 挂起等待锁(自适应)
- 不是读写锁
- 非公平锁
- 可重入锁
synchronized
的加锁过程,尤其是搞清楚 “自适应” 是怎么回事:
-
偏向锁阶段
- 核心思想:能不加锁就不加,但是一旦有其他线程来争这个锁,则立马对该资源加锁。
也就是当只有一个线程在调用这个带有锁的资源的时候,
synchronized
只是对该锁作一个标记,并没有真的锁上;一旦有其他线程来 “抢” 这个锁,它就会在这之前把锁获取并持有,此时就从偏向锁升级到轻量级锁。 -
轻量级锁阶段
- 假设有锁竞争,但是竞争不激烈的情况下:
- 此处就实现了 “自旋锁”
- 优势在于:其他线程释放锁之后,可以在第一时间获取并持有锁;不会有调度的开销。
- 劣势在于:因为没有受到调度,所以一直在消耗CPU的资源
- 于此同时,
synchronized
内部也会统计当前这个锁对象上有多少个线程在参与竞争,当发现参与竞争的线程过多时,就会进一步升级成 “重量级锁”。
-
重量级锁阶段
- 此时拿不到锁的线程就不会 “自旋”,而是进入 “阻塞等待” 状态,会被调度出CPU。
- 当锁被释放时,就由系统随机唤醒一个线程来获取锁。
注:锁阶段只能 “升级”,而不能 “降级”,严格来说自适应这一说法是不太严谨的。
synchronized
的优化策略:
- 锁消除:当编译器在编译代码时,如果发现该代码不需要加锁,就会自动将这个锁去掉。
- 锁粗化:会把多个 “细粒度” 的锁,合并成一个 “粗粒度” 的锁。
粗细:一般认为 synchronized() {......}
花括号里面包含的代码越少,锁的粒度越细;如果包含的代码越多,锁的粒度就越粗。
ReentrantLock
ReentrantLock
与 synchronized
大致是相同的,但具备着一些 synchronized
没有的功能:
-
ReentrantLock
提供了tryLock()
操作synchronized
的加锁是直接进行加锁,如果加锁不成功,则让线程进入阻塞等待。tryLock()
是尝试进行加锁,如果加锁不成功,不会让线程进入阻塞,而是直接返回false
-
ReentrantLock
提供了公平锁的实现synchronized
是非公平锁。ReentrantLock
可以通过队列来记录加锁线程的先后顺序,实现公平锁
-
对等待通知机制的优化:
- 对于
synchronized
来说,搭配了wait()
和notify()
方法。如果多个线程使用同一个锁的wait()
方法,那么 在唤醒时,是 “随机” 唤醒一个线程。 - 而对于
ReentrantLock
来说,搭配Condition
类,就可以实现 “指定” 唤醒某个线程。
- 对于