ReentrantLock加锁解锁的过程

编程入门 行业动态 更新时间:2024-10-12 05:52:35

ReentrantLock加锁<a href=https://www.elefans.com/category/jswz/34/1771119.html style=解锁的过程"/>

ReentrantLock加锁解锁的过程

文章目录

    • 简介
    • 与synchronized相比
      •     相同点
      •     不同点
    • 构造方法
    • 加锁
    • 解锁
    • 公平锁和非公平锁的区别

简介

    ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种保证线程并发安全的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全.

与synchronized相比

    相同点

  1. 都是独占锁的实现
  2. 都是可重入锁;同一线程可以多次获得同一个锁
  3. 都保证了可见性和互斥性
  4. 阻塞等待中的线程来实现同步:也就是说当一个线程获得了锁,进入了同步代码块,其他访问的线程都必须阻塞在同步代码块外面进行等待,而线程阻塞和唤醒的代价是很高的(线程阻塞以后进入内核态,我们的JVM运行在用户态,当我们唤醒线程操作系统需要通过CPU要在用户态与内核态进行一轮切换,是一个非常重型的操作);

    不同点

比较的点synchronizedReentrantLock
获得锁的方式隐式锁,jvm控制加锁解锁显式锁,手动加锁解锁
是否公平非公平锁既可以公平锁,也可以非公平锁,通过构造方法传入的boolean判断
锁的对象锁的是对象,锁是保存在Mark Word里面,根据Mark Word数据标识锁当前的状态锁的是线程,根据进入的线程和AQS内部维护的state同步器标识锁当前的状态

构造方法


    /*** 使用内部类Sync来保证同步* Sync继承AQS框架,有两个子类* NonfairSync:非公平锁* FairSync:公平锁*/private final Sync sync;/*** 无参构造无参构造默认使用非公平锁*/public ReentrantLock() {sync = new NonfairSync();}/*** 带参构造* @param fair true:公平锁* 			   false:非公平锁*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}


ReentrantLock通过内部类Sync来保证线程安全,Sync继承AbstractQueuedSynchronized,还对该抽象类的部分方法做了实现;并且还定义了两个子类:

  1. FairSync 公平锁
  2. NonfairSync 非公平锁

    这两个类都继承Sync,相当于间接的继承了AbstractQueuedSynchronized,所以ReentrantLock同时具备公平与非公平两个特性

     AbstractQueuedSynchronized这个类里面维护了一个state属性,这个属性是int类型的,被volatile关键字修饰,记录的当前锁的状态,这个类里面还有一个内部类Node,Node是一个双向链表,AbstractQueuedSynchronized这个类里面记录着等待队列中的头节点与为节点.主要用于来实现等待队列.

    在上面的继承关系图上面还有一个类是非常重要的就是AbstractOwnableSynchronizer,这个类里面维护了一个属性:exclusiveOwnerThread,这个属性是Thread类型的,记录的就是当前拿到锁的线程;

加锁

/*** @Classname ReentrantLockDemo* @Description* @Date 2021/8/31 22:01* @Author fanqiechaodan*/
@Slf4j
public class ReentrantLockDemo {public static void main(String[] args) {// 公平锁ReentrantLock lock = new ReentrantLock(true);for (int i = 0; i < 10; i++) {new Thread(() -> {try {lock.lock();log.info("加锁成功");} finally {lock.unlock();}}).start();}}
}
  1. 会先调用ReentrantLock.lock()
  2. 由于在创建ReentrantLock对象时,参数传的是true,所以下一步就会调用公平锁FairSync.lock()
  3. 方法里面有接着调用AbstractQueuedSynchronizer.acquire(int arg);并且传了一个固定的参数1

    3.1 acquire方法里面第一步就是调用tryAcquire(arg),并把参数1传了进去,看到本类中的tryAcquire方法并没有任何的实现,他的实现都是在子类中完成的,这个类中只负责定义具体的行为;到最后还是会调用FairSync.tryAcquire(),这个方法是进行尝试加锁,整个加锁的逻辑都在这里
		// FairSync.tryAcquire()公平锁尝试加锁方法protected final boolean tryAcquire(int acquires) {// 拿到当前线程final Thread current = Thread.currentThread();// 获取state当前锁的状态,int c = getState();if (c == 0) {// 如果c为0,就代表当前可以获取锁// 因为现在是公平锁,hasQueuedPredecessors():要先去查询有没有线程在排队,如果有当前线程是不是第一个//compareAndSetState(0, acquires):使用cas的方式将state设置为acquires(这个参数就是1)// 然后将AbstractOwnableSynchronizer这个类中的exclusiveOwnerThread属性设置为当前线程if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);// 所有操作都做完以后加锁成功return true;}}else if (current == getExclusiveOwnerThread()) {// 如果c不等于0但是此时占用锁的线程就是当前线程// 对state+1然后设置state,代表ReentrantLock是具备可重入性的// 这段代码直接使用加号,本身就是线程安全的,// 因为有前面的判断会保证只有一个线程能进来int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);// 加锁成功return true;}// 当前锁已经被占用并且不是当前线程占用的,加锁失败return false;}

3.2 经过上一步加锁的方法,加锁成功的会去执行我们的业务代码,没有加锁成功的就会执行AbstractQueuedSynchronizer.acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

     //把当前线程创建一个节点,传了一个Node参数Node.EXCLUSIVE,上面有讲过是独占模式private Node addWaiter(Node mode) {// 把当前线程当作构造参数新建一个NodeNode node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failure// 把尾节点的指针给predNode pred = tail;// tail指针从来都没有被赋值过,// pred是一定为null的!=null里面的逻辑永远都不会走if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}// 将节点插入等待队列enq(node);return node;}
    // 将节点插入等待队列的方法private Node enq(final Node node) {// 死循环(自旋)保证一定可以将当前节点插入到等待队列中for (;;) {Node t = tail;if (t == null) { // Must initialize// 如果队列没有进行初始化,就对队列进行初始化if (compareAndSetHead(new Node()))tail = head;} else {// 自旋逻辑,队列初始化后一直会走elsenode.prev = t;// cas方式插入队列if (compareAndSetTail(t, node)) {t.next = node;return t;}}}}
	// 将创建好的节点传进来final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {// 获取当前节点final Node p = node.predecessor();// 判断一下当前节点是不是头节点// 如果是头节点的话,再去尝试的获取锁// 这样做是为了不想马上阻塞线程,// 因为阻塞/唤醒需要用户态到内核态切换,是一个重型操作尽量避免if (p == head && tryAcquire(arg)) {// 加锁成功// 把当前节点设置为头部节点,方法里面会把当前节点置为null// 相当于从队列中删除掉当前节点setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// shouldParkAfterFailedAcquire(p, node):判断一下当前节点是不是可以被唤醒的// 判断逻辑会执行两次// 首先第1轮循环、修改head的状态,修改成sinal=-1标记处可以被唤醒.//第2轮循环,阻塞线程,并且需要判断线程是否是有中断信号唤醒的!// parkAndCheckInterrupt():将当前节点阻塞住,底层调用的LockSupport.park(this)实现的if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

解锁


    public final boolean release(int arg) {if (tryRelease(arg)) {// 解锁成功Node h = head;// 判断一下还有没有在等待的线程// 如果有看一下当前线程是不是可以唤醒的if (h != null && h.waitStatus != 0)// 如果有等待的线程并且可以被唤醒就唤醒线程// 底层调用的LockSupport.unpark(s.thread);unparkSuccessor(h);return true;}return false;}

  • 解锁不管公平还是非公平使用的都是Sync.tryRelease(int releases),调用的时候参数传的都是1
        protected final boolean tryRelease(int releases) {// 把state减掉1int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;// 把当前占有锁的线程置为nullsetExclusiveOwnerThread(null);}//将c设置为statesetState(c);return free;}

公平锁和非公平锁的区别



通过对比公平锁与非公平锁的加锁代码,可以看得出来两个内部类的加锁方法基本上来说是一摸一样的,唯一不同的是,当发现锁没有被占用时:

  • 非公平锁:直接采用cas的方式设置锁的状态,并且将占用锁的线程设置为当前线程,然后返回加锁成功
  • 公平锁: 先执行!hasQueuedPredecessors()代码,这段代码主要是为了看一下等待队列中有没有正在等待的线程,如果有,那么判断一下当前线程是不是排在最前面的线程,如果是最前面的线程,就使用cas的方式设置锁的状态,并且将占用锁的线程设置为当前线程然后返回加锁成功
    可以获取锁的时候,非公平锁会马上获取锁,不会考虑等待队列中有没有比当前线程排队更靠前的线程,直接进行加锁;公平锁则会考虑等待队列中有没有比当前队列更靠前的线程,如果有当前线程就不能加锁.

更多推荐

ReentrantLock加锁解锁的过程

本文发布于:2023-07-28 19:27:03,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1287085.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:解锁   加锁   过程   ReentrantLock

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!