可重复锁ReentrantLock原理分析

可重入锁ReentrantLock实现层面依赖

一、CAS(compareAndSet)

LockSupport

基本的方法

park

park使得当前线程放弃cpu 进入等待(waiting)状态 操作系统不会再对其进行调度

直到其他线程对它调用了unpark方法,其中unpark方法使得参数指定的线程恢复可运行状态

[1] part和Thread.yield()区别

  • yield 只是告诉操作系统可以让其他线程先运行,但是自己可以仍是运行态
  • park 方法则是放弃线程的运行资格,使得线程进入 WAITING 等待状态

[2] 响应中断

park 方法是响应中断的,当有中断发生时,park方法会返回,并且重新设置线程的中断状态

[3]2个变体

  • parkNanos: 可以指定等待的最长时间,参数是相对于当前时间的纳秒数
  • parkUntil:可以指定最长等待的时间,参数是绝对时间,相对于纪元时的毫秒数

当等待超时,方法就会返回。同时还有一些其他的变体,可以指定一个对象,表示是由于该对象而进行等待,以便于调试,一般情况下传递的参数为 this

getBlocker

二、AQS

  • 提供了一个state字段 被volatile修饰 保证内存可见性、顺序性

  • AQS内部维护了一个等待队列,借助CAS方法实现无阻塞算法进行更新

三、ReentrantLock

Sync是抽象类

NonfairSync是 fair 为 false 时使用的类[默认]

FairSync 是 fair 为 true 时需要使用的类

lock实现

该方法被子类重写

如果没有被锁定,则使用CAS进行锁定;如果当前线程已经被锁定,则增加锁定次数。

如果 tryArquire方法返回false,则acquire方法会继续调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。

其中,addWaiter 会新建一个节点 Node,代表当前线程,然后加入内部的等待队列中。

在当如等待队列之后,调用 acquireQueued 来尝试获取锁,其代码为

是一个死循环,在每次循环中,首先检查当前节点是否为第一个等待的节点,

如果是且能获取到锁,就将当前节点从等待队列中移除并且返回,

否则通过parkAndCheckInterrupt方法最终调用 LockSupport.park而放弃CPU,

进入等待状态,在被唤醒之后检查是否发生了中断,记录中断标志。并且返回中断标志

如果能获得锁则立即获得,如若不能则加入等待队列。被唤醒之后检查自己是否为第一个等待的线程,如果是且能获得锁则返回,否则继续等待。如果在该过程中发生了中断, lock 会记录中断标志位,但是不会提前返回或者抛除异常

unlock实现

tryRelease 方法会修改线程状态并且释放锁, unparkSuccessor 方法会调用 LockSupport.unpark 将第一个等待的线程唤醒

公平锁和非公平锁

公平锁比非公平锁在源码实现上就多了一个检查:当没有其他等待时间更长的线程时,才能获取到锁

公平锁模型

初始化时, state=0,表示没有线程过来抢锁。这时候,A线程请求锁,占了锁,把state+1

线程A取得了锁,把 state原子性+1,这时候state被改为1,A线程继续执行其他任务,然后线程B请求锁,线程B无法获取锁,生成节点进行排队

初始化的时候,会生成一个空的头节点,然后才是B线程节点,这时候,如果线程A又请求锁,是否需要排队?答案当然是否定的,否则就直接死锁了。当A再次请求锁

可重入锁:就是一个线程在获取了锁之后,再次去获取了同一个锁,这时候仅仅是把状态值进行累加。如果线程A释放了一次锁

仅仅是把状态值减了,只有线程A把此锁全部释放了,状态值减到0了,其他线程才有机会获取锁。当A把锁完全释放后,state恢复为0,然后会通知队列唤醒B线程节点,使B可以再次竞争锁。当然,如果B线程后面还有C线程,C线程继续休眠,除非B执行完了,通知了C线程。注意,当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除

非公平锁模型

当线程A执行完之后,要唤醒线程B是需要时间的,而且线程B醒来后还要再次竞争锁,所以如果在切换过程当中,来了一个线程C,那么线程C是有可能获取到锁的,如果C获取到了锁,B就只能继续休眠了

为什么不默认是公平锁

保证公平整体性能会比较低,其原因不是因为检查慢,而是因为会让活跃线程无法得到锁,从而进入等待状态,引起了频繁的上下文切换,降低了整体的效率

ReentrantLock tryLock()方法使用的是非公平锁

和synchronized比较

  • ReentrantLock可以实现与 synchronized 相同的语义 而且支持以非阻塞方式获取锁,也可以想用中断,限时阻塞,更为灵活;synchronized 的使用更为简单,代码量也更少
  • synchronized 代表的是一种声明式编程思维 由 Java 系统负责实现 程序员并不清楚实现细节;显式锁代表一种命令式编程思维,使用者需要实现所有的细节
  • 声明式编程的好处除了简单,在性能上也有所体现。 在较新版本的 JVM 上,ReentrantLock和synchronized的性能是接近的, 并且 Java 编译器和虚拟机会不断优化 synchronized 的实现,比如自动分析 synchronized 的使用,对于没有锁竞争的场景,自动忽略对获取锁/释放锁的调用
  • 能用 synchronized 就用 synchronized,不满足使用要求的时候考虑使用 ReentrantLock

代码资源

https://gitee.com/pingfanrenbiji/myconcurrent/blob/master/src/main/java/pers/hanchao/concurrent/reentrantLock/LockSupportTest.java

参考文章

https://blog.csdn.net/u011669700/article/details/80070892

https://blog.csdn.net/yanyan19880509/article/details/52345422

本文使用 mdnice 排版

以上是 可重复锁ReentrantLock原理分析 的全部内容, 来源链接: utcz.com/a/25997.html

回到顶部