并发源码解析(四)--ReentrantLock

本文详细分析了ReentrantLock的源码,包括其可重入特性、公平锁与非公平锁的实现。公平锁在无等待节点时才能获取锁,否则会入队等待;非公平锁在lock后会多次尝试获取锁,即使失败也会尝试入队。ReentrantLock的构造方法、lock方法及释放锁的过程也进行了探讨。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、ReentrantLock

ReentrantLock是可重入的独立锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被加入该锁的AQS阻塞队列里面。

可以实现公平锁和非公平锁

二、源码分析

2.1 继承关系

在这里插入图片描述
实现了lock接口和序列化接口
在这里插入图片描述
还有3个静态内部类

其中的关系如下:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
其中Sync的lock方法为抽象方法,交给子类去实现

2.2 构造方法

无参构造方法

public ReentrantLock() {
	//执行锁为非公平锁
    sync = new NonfairSync();
}

带布尔值参数的构造方法

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2.3 常用方法

2.3.1 公平锁的lock方法

先分析非公平锁吧

    public void lock() {
    	//交给Sync的子类实现
        sync.lock();
    }

非公平锁的lock实现

     final void lock() {
     	//使用CAS方式获取锁
         if (compareAndSetState(0, 1))
         	 //获取成功设置当前线程名
             setExclusiveOwnerThread(Thread.currentThread());
         else
         	//获取锁失败,去等待队列
             acquire(1);
     }

加入等待队列,其实就是之前讲的AQS里的方法,具体就不相信说了。只说一下子类实现的tryAcquire方法

public final void acquire(int arg) {
	//如果tryAcquire返回false,则执行&&右边语句,线程加入阻塞队列
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire方法在AQS是抽象的,由子类执行实现

//尝试获取锁
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

//非公平方式尝试获取
final boolean nonfairTryAcquire(int acquires) {
	 //获得当前线程名
     final Thread current = Thread.currentThread();
     //获取锁的状态
     int c = getState();
     //等于0代表锁已释放
     if (c == 0) {
     	//尝试获取锁
         if (compareAndSetState(0, acquires)) {
         	 //成功则设置线程名
             setExclusiveOwnerThread(current);
             //返回true代表成功
             return true;
         }
     }
     //如果线程名和当前使用锁的线程相等,则重入锁
     else if (current == getExclusiveOwnerThread()) {
     	 //c + 1
         int nextc = c + acquires;
         //如果+1后为负数,溢出了,抛异常
         if (nextc < 0) // overflow
             throw new Error("Maximum lock count exceeded");
 		 //设置锁的state为nextc	            
         setState(nextc);
         //返回成功
         return true;
     }
     //获取锁失败
     return false;
 }

以上是非公平锁的lock方法,可以看出,在lock()方法后,马上争夺所资源,在进入队列前的tryAcquire方法中再次尝试争夺所资源,而入队后还要再抢夺一次才挂起。

入队列挂起前争夺了两次锁

2.3.2 公平锁的lock方法

再来看一下公平锁,构造方法参数为true即为公平锁

final void lock() {
    acquire(1);
}

//直接进入了这里,看一下tryAcquire方法
public final void acquire(int arg) {
	//tryAcquire失败则进去阻塞队列
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
	  //这里同上
      final Thread current = Thread.currentThread();
      int c = getState();
      //如果锁已经被释放
      if (c == 0) {
      	  //判断有无前驱节点
          if (!hasQueuedPredecessors() &&
              compareAndSetState(0, acquires)) {
              //没有则获取锁
              setExclusiveOwnerThread(current);
              return true;
          }
      }
      //重入锁
      else if (current == getExclusiveOwnerThread()) {
          int nextc = c + acquires;
          if (nextc < 0)
              throw new Error("Maximum lock count exceeded");
          setState(nextc);
          return true;
      }
      //获取锁失败
      return false;
  }
}

可以看出非公平锁抢夺比较激烈,而公平锁则是lock方法并不争夺锁,而在tryAcquire方法中判断即使锁已经是释放的,也要先判断阻塞队列中有无节点,如果队列中有节点,则入队等待

2.3.3 释放锁

public void unlock() {
    sync.release(1);
}

//使用的是AbstractQueuedSynchronizer里的release方法
public final boolean release(int arg) {
	//tryRelease是子类自己实现的,看下这个方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

//Sync抽象类实现了此方法,传入的releases为1
protected final boolean tryRelease(int releases) {
	//锁的state-1
    int c = getState() - releases;
    //如果线程名和锁的线程名不一致,抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    //锁释放标志
    boolean free = false;
    //只有c等于0锁才释放
    if (c == 0) {
        free = true;
        //释放锁后设置线程名为null
        setExclusiveOwnerThread(null);
    }
    //未释放锁,设置c的大小
    setState(c);
    //返回锁是否释放成功
    return free;
}

释放锁就比较简单了

三、总结

之前没看源码,才看才发现非公平锁和公平锁和之前想的不太一样。

公平锁只在没有队列节点的情况下才直接拿锁,否则入队

非公平锁,在lock后随即抢锁,失败后。在入队前再次抢夺锁资源,入队后挂起前再次抢夺一次,还是失败就挂起。

但是在阻塞对立中等待的节点,非公平锁就没办法了,只能按照队列的顺序依次唤醒

以上是对ReentrantLock的理解,分析的是最基本的方法。如果后面有空就在分析下别的方法。

下面是一个ReentrantLock的生产者消费者模型

package com.cf.test.lock;

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TestDemo {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        Condition notEmpty = lock.newCondition();
        Condition notFull = lock.newCondition();

        LinkedList<String> list = new LinkedList<>();
        int listSize = 10;

        new Thread(() -> {
            lock.lock();
            try {
                while (true) {
                    while(list.size() == listSize){
                        notEmpty.await();
                    }
                    list.add("element");
                    System.out.println("加入了元素,现在队列大小是:" + list.size());
                    notFull.signalAll();
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }, "生产者").start();

        new Thread(() -> {
            lock.lock();
            try {
                while (true) {
                    while(list.size() == 0){
                        notFull.await();
                    }
                    String s = list.poll();
                    System.out.println("取出了元素:" + s);
                    notEmpty.signalAll();
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }, "消费者").start();

    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值