ReentrantLock底层实现

ReentrantLock 实现了 Lock 接口,是 AQS 的一种。加锁和解锁都需要显式写出,注意结束操作记得 unlock 释放锁。它内部自定义了同步器 Sync,这个又实现了 AQS,同时又实现了 AOS,而后者就提供了一种互斥锁持有的方式。其实就是每次获取锁的时候,看下当前维护的那个线程和当前请求的线程是否一样,一样就可重入了。它还提供了获取共享锁和互斥锁的方式,都是基于 CAS 对 state 操作而言的。

ReentrantLock除了可重入还有哪些特性

  • 支持线程中断,只是在线程上增加一个中断标志 interrupted ,并不会对运行中的线程有什么影响,具体需要根据这个中断标志干些什么,用户自己去决定。比如,实现了等待锁的时候,5秒没有获取到锁,中断等待,线程继续做其它事情。
  • 超时机制,在 ReetrantLock::tryLock(long timeout, TimeUnit unit) 提供了超时获取锁的功能。它的语义是在指定的时间内如果获取到锁就返回true,获取不到则返回false。这种机制避免了线程无限期的等待锁释放。

    ReentrantLock使用场景

  • 场景1:如果已加锁,则不再重复加锁,多用于进行非重要任务防止重复执行,如,清除无用临时

文件,检查某些资源的可用性,数据备份操作等

  • 场景2:如果发现该操作已经在执行,则尝试等待一段时间,等待超时则不执行,防止由于资源处

理不当长时间占用导致死锁情况

  • 场景3:如果发现该操作已经加锁,则等待一个一个加锁,主要用于对资源的争抢(如:文件操

作,同步消息发送,有状态的操作等)

  • 场景4:可中断锁,取消正在同步运行的操作,来防止不正常操作长时间占用造成的阻塞

Synchronized 和 Lock 区别

https://www.yuque.com/docs/share/0396826a-984d-498b-af1f-f944f523084c?# 《Synchronized 和 Lock 区别》

Lock 简介

Lock api 如下

  1. void lock();
  2. void lockInterruptibly() throws InterruptedException;
  3. boolean tryLock();
  4. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  5. void unlock();
  6. Condition newCondition();

其中最常用的就是 lock 和 unlock 操作了。因为使用 lock 时,需要手动的释放锁,所以需要使用 try..catch 来包住业务代码,并且在 finally 中释放锁。典型使用如下

  1. private Lock lock = new ReentrantLock();
  2. public void test(){
  3. lock.lock();
  4. try{
  5. doSomeThing();
  6. }catch (Exception e){
  7. // ignored
  8. }finally {
  9. lock.unlock();
  10. }
  11. }

锁的可重入

当一个线程递归调用的时候如果不能实现可重入的话,意味着这个线程递归再进入这个方法我就拿不到了.就会自己把自己锁死

同一个线程对于已经获得到的锁,可以多次继续申请到该锁的使用权,synchronized关键字隐式的支持重进入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁。

一个线程如果获取了ReentrantLock这个锁,那么他在下一次进入这个方法的时候可以继续拿到同一把锁.ReentrantLock在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

公平锁和非公平锁

公平锁:是指多个线程按照申请锁的顺序来获取锁,在并发坏境中.每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁.否则就会加入到等待队列中.以后会按照FIFO的规则从队列中取到自己, 类似排队打饭

非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象, 非公平锁比较粗鲁,上来就直接尝试占有锁。

在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:

在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程A持有一个锁,并且线程B请求这个锁。由于这个锁已被线程A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此会再次尝试获取锁。与此同时,如果C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面:B获得锁的时刻并没有推迟,C更早地获得了锁,并且吞吐量也获得了提高。

虽然公平锁在公平性得以保障,但因为公平的获取锁没有考虑到操作系统对线程的调度因素以及其他因素,会影响性能。
虽然非公平模式效率比较高,但是非公平模式在申请获取锁的线程足够多,那么可能会造成某些线程长时间得不到锁,这就是非公平锁的“饥饿”问题。
但大部分情况下我们使用非公平锁,因为其性能比公平锁好很多。但是公平锁能够避免线程饥饿,某些情况下也很有用。

ReentrantLock和synchronized是公平锁还是非公平锁?


synchronized 是非公平锁(出于性能考虑)

ReentrantLock 通过构造方法指定该锁是否为公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大.

Lock lock = new ReentrantLock(); //非公平锁
Lock lock = new ReentrantLock(true); //公平锁