Java多线程篇(6)——AQS之独占锁(ReentrantLock)

1、管程

什么是管程?
管理协调多个线程对共享资源的访问,是一种高级的同步机制。

有哪些管程模型?
hansen:唤醒其他线程的代码必须在当前线程的最后执行,以确保其他线程被唤醒时,当前线程已经执行完。
hoare:唤醒其他线程的代码可以在任意位置,且唤醒其他线程后,当前线程立即阻塞,当被唤醒线程执行完后,再继续执行当前线程。
mesa:唤醒其他线程的代码可以在任意位置,且被唤醒线程不会立即执行,而是加入一个队列,当当前线程执行完后,再从队列中获取并执行其他线程。(需要注意的是,由于存在时间差,所以真正执行的时候有可能已经不满足条件)

其中最常用的管程模型就是mesa,在java中实现的也是该模型。如下图是AQS的实现:
在这里插入图片描述
其实管程模型在java里的实现不止有AQS,synchronized 在jvm的底层实现像什么 cxq,entryList,waitSet也是mesa管程模型中的概念,同理object.wait()/object.notify()也是管程模型的实现。

2、AQS

所以具体什么是aqs?
aqs就是java代码对管程模型的一个抽象实现。把volatile state字段定义成共享资源,并且实现了同步等待队列和条件等待队列的入队/出队以及线程的阻塞/唤醒等公共操作,至于具体共享资源的获取/释放则交由各自实现类,不同的实现类可以定义不同的共享资源获取方式,由此可以实现公平锁/非公平锁,重入锁/不可重入锁,独占锁/共享锁等以满足不同的场景。

简单的看个印象
在这里插入图片描述

共享资源:
在这里插入图片描述
同步等待队列:
在这里插入图片描述
条件等待队列:
在这里插入图片描述
获取共享资源:
在这里插入图片描述
释放共享资源:
在这里插入图片描述
阻塞:
在这里插入图片描述
唤醒:
在这里插入图片描述

3、ReentrantLock

AQS最经典的实现莫过于 ReentrantLock。接下来看看是 ReentrantLock 如何通过AQS实现 lock/unlock 的。

3.1、lock/unlock

3.1.1、lock

AbstractQueuedSynchronizer.acquire
获取共享资源模板(aqs实现)
在这里插入图片描述

ReentrantLock .tryAcquire
共享资源获取逻辑(即ReentrantLock 自己实现的获锁方法)

在ReentrantLock中实现了公平/非公平两种获锁方式(默认为非公平)

以为非公平为例:

		@ReservedStackAccess
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //cas修改共享资源
                if (compareAndSetState(0, acquires)) {
                    //修改成表示获锁成功,设置当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //可重入锁的实现
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

AbstractQueuedSynchronizer.addWaiter
获锁失败后入队同步队列(aqs实现)

    private Node addWaiter(Node mode) {
        Node node = new Node(mode);

        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                //cas加入队列
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                //初始化队列
                initializeSyncQueue();
            }
        }
    }

AbstractQueuedSynchronizer.acquireQueued
入队后阻塞之前,根据前节点的状态进行一定次数的自旋获锁(aqs实现)

	final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
        	//这个循环保证一定会获取到锁
            for (;;) {
                final Node p = node.predecessor();
                //如果前一个节点是头结点,再次尝试获锁
                if (p == head && tryAcquire(arg)) {
                    //如果获锁成功就出队头结点
                    setHead(node);
                    p.next = null;
                    return interrupted;
                }
                //如果获锁失败或者前节点不是head的节点就根据前节点的状态来看是否需要阻塞
                //需要阻塞就调用 LockSupport.park() 阻塞线程
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }


    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        
        //如果前驱节点状态为 SIGNAL 直接返回 true 阻塞线程
        if (ws == Node.SIGNAL)
            return true;
               
        //如果前驱节点状态大于0,说明线程已被返回,剔除无用节点前驱节点
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } 

		//cas替换前驱节点状态为 SIGNAL
		else {
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        
        return false;
    }

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

也就是说,获锁失败进入同步等待队列进行阻塞,而在实际阻塞之前会自旋再次尝试获锁。

实际上在自旋途中只有前节点是head的节点才会尝试获锁
如果前节点的 waitStatus 为 signal 则停止自旋
为避免无限自旋,自旋的同时会尝试修改前节点的状态为 signal

3.1.2、unlock

AbstractQueuedSynchronizer.release
释放共享资源模板(aqs实现)
在这里插入图片描述

h.waitStatus == 0 说明后面没有等待唤醒的节点

ReentrantLock .tryRelease
共享资源释放逻辑(即ReentrantLock 自己实现的释放锁方法)

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //直接修改state值(不需要cas或者什么操作,因为释放锁不可能有并发)
            setState(c);
            return free;
        }

AbstractQueuedSynchronizer.unparkSuccessor
唤醒节点(aqs实现)

private void unparkSuccessor(Node node) {
        //清除node节点的waitStatus,重置为0
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);

        //如果node节点的后继节点被取消或者为空,就从尾部向前遍历找到实际的未取消后继节点。
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        
        //LockSupport.unpark
        if (s != null)
            LockSupport.unpark(s.thread);
    }

3.2、一些思考

1、公平锁和非公锁的区别在哪?
在这里插入图片描述

2、如何实现可中断的?
在这里插入图片描述

3、如何实现可超时的?
在这里插入图片描述

4、一开始没有线程获取锁,第一获取锁的线程进来直接获锁成功返回,没有入队操作,如何唤醒后继的线程?
答:虽然获锁线程没有入队,但是如果后续有等待线程需要用到队列的话还是会new一个node用于表示之前获锁线程的(相当于之前的获锁线程入队了)。
在这里插入图片描述
5、被唤醒的节点如何出队?
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值