锁的可重入

  • 同一个线程对于已经获得到的锁,可以多次继续申请到该锁的使用权。
  • synchronized关键字隐式的支持重进入。
  • ReentrantLock在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞


为什么要有锁的可重入?


  1. public class LockTest {
  2. private ReentrantLock lock = new ReentrantLock();
  3. public void m1(){
  4. lock.lock();
  5. try {
  6. m2();
  7. }finally {
  8. lock.unlock();
  9. }
  10. }
  11. private void m2() {
  12. //既然m1已经加锁了,为什么m2还需要加锁?不加锁一样能够保证m1调用m2的安全性
  13. lock.lock();
  14. try {
  15. System.out.println("同步代码块");
  16. }finally {
  17. lock.unlock();
  18. }
  19. }
  20. }

在这段代码中,m1调用了m2,既然m1已经加锁了,为什么还要给m2加锁?
原因是 既然m2独立出来成了一个方法,你就没法保证你在调用m1的方法的同时,别的人不通过m1调用m2,而是直接调用m2。如果m2不加锁,这里就必然会出现线程安全问题。

ReentrantLock

ReentrantLock的内部类Sync又有两个内部类NonfairSync非公平锁与FairSync公平锁,默认是**非公平锁**,通过构造方法传入一个boolean类型的值可以决定创建的锁的类型。
在ReentrantLock中的state表示当前获取到锁的线程的锁的可重入的数量,每一次加一。
image.png

FairSync 公平锁

image.png

NonfairSync 非公平锁

image.png

ReentrantLock执行逻辑

锁的获取

image.png
1. 尝试获取对象的锁(tryAcquire),如果获取不到(意味着已经有其他线程持有了锁,并且尚未释放),那么它就会进入到AQS的阻塞队列当中。

公平锁 要排队,尝试失败进入阻塞队列

image.png

hasQueuedPredecessors 判断当前线程之前是否还有线程在等待

非公平锁 直接去获取,不排队,尝试失败进入阻塞队列

image.png
2. 如果获取到,那么根据锁是公平锁还是非公平锁来进行不同的处理
2.1 如果是公平锁,那么线程会直接放置到AQS阻塞队列的末尾
2.2 如果是非公平锁,那么线程会首先尝试进行CAS计算,
如果成功,则直接获取到锁;
如果失败,则与公平锁的处理方式一致,被放到阻塞队列末尾

锁的释放

判断头结点是否为空
image.png

image.png
3. 当锁被释放时(调用了unlock方法),那么底层会调用release方法对state成员变量值进行减一操作,
如果减一后,state值不为0, 那么release操作就执行完毕;
如果减一操作后,state值为0,则调用LockSupport的unpark方法唤醒该线程后的等待队列中的第一个,即后继线程(pthread_mutex_unlock),将其唤醒,使之能够获取到对象的锁(release时,对于公平锁与非公平锁的处理逻辑是一致的); 之所以调用release方法后state值可能不为零,原因在于ReentrantLock是可重入锁,表示线程可以多次调用lock方法, 导致每调用一次,state值都会加一

对于ReentrantLock来说,所谓的上锁,本质上就是对AQS中的state成员变量的操作:对该成员变量+1,表示上锁对该成员变量-1,表示释放锁