Lock体系及深入里理解AQS

本文探讨了JDK1.5引入的Lock体系,与内建锁的区别在于其可中断获取锁和超时响应机制。AQS作为核心组件,用于构建锁和其他同步器件,由同步状态和FIFO队列构成。Lock接口提供了使用者与锁的交互,而AQS面向实现者,管理同步状态和线程操作。Lock实现通常通过继承AQS并覆盖protected方法。以ReentrantLock为例,lock方法调用sync.lock(),在acquire()方法中进行节点添加、同步队列管理和状态获取。释放锁的过程也是关键部分。

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

Lock体系是JDK1.5增加的,它访问共享资源的访问机制与内建锁完全不同!
它没有内建锁隐式的加锁与释放锁,它增加了可中断获取锁及超时响应机制获取锁的特性,这些特性是内建锁完全不具备的!

AQS:是Lock体系核心的存在,是构建锁与其他同步器件的基础框架,是由一个int整型表示其同步状态及FIFO队列组成,一般使用AQS都会创建一个静态内部类继承AQS。

Lock–>面向使用者,定义了使用者与锁的接口,隐藏了实现细节
AQS–>面向实现者,对使用者屏蔽了同步状态的管理,线程的排队,等待,唤醒等机制;

Lock体系可以有多个等待队列,而内建锁只有一个等待队列;
等待队列:调用wait()方法被阻塞的线程

同步队列:获取锁失败的线程进入同步队列,Lock体系的同步队列是由一个带有头尾指针的双向链表组成,是把线程封装成一个Node节点放在链表中;

Lock接口的API

1.void lock();
2.boolean tryLock();//获取锁成功返回true,否则返回false
3.boolean tryLock(long,TimeUnit)// 超时等待获取锁,在二的基础上进行重载
4.void lockInterruptibly() throws InterruptedException;获取锁的过程中可以响应中断
5.void unLock()//解锁

Lock接口的常用实现子类

1.ReentrantLock  //可重入锁
2.ReentrantReadWriteLock //可重入读写锁

AQS中的protected方法-需要被子类覆写

1.tryAcquire()--独占式获取锁,成功返回true
2.tryRelease()--释放锁 成功返回true
3.isHeldExclusively()--当前线程是否持有锁

一般Lock接口的实现子类里都会有一个静态内部类继承AQS,覆写AQS中的提供给子类覆写protected方法,然后实现,所以最后在调用AQS中的方法时其实就会调用子类实现的方法!(这里用到了模板设计方法)

ReentrantLock里的lock方法,很显然直接调用了sync.lock()!
1.

public void lock() {
        sync.lock();
    }
sync.lock()-----》
   
final void lock() {
            //先通过CAS尝试将锁的状态设置为自己的线程状态           
            if (compareAndSetState(0, 1))
                //设置成功的话将当前同步状态改为自己的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //不成功调用acquire()方法
                acquire(1);
        }

acquire(int arg)方法–>独占式 获取锁的模板方法
调用AQS的acquire(int arg)

 public final void acquire(int arg) {
        //尝试获取锁,如果成功则直接返回,否则继续向下执行
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

addWaiter(Node.EXCLUSIVE),从方法的名字可以大致看出来,加入同步队列,也就是刚才获取同步状态失败的锁加入进去

 private Node addWaiter(Node mode) {
 ///将当前线程尾插入同步队列
 //mode是模式的意思,在Lock体系中有两大模式,独占式和共享式,这里需要知道传进来的参数独占式还是共享式
       //将当前线程以指定的模式封装成Node节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //拿到当前同步队列的尾节点
        Node pred = tail;
        //如果当前尾节点为不为空,则进行尾插法
        if (pred != null) {
            node.prev = pred;
            //将当前节点尾插入同步队列
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //否则,调用enq(node)方法
        enq(node);
        return node;
    }

若当前同步队列为null或者尾插失败会调用enq()
当同步队列为null时初始化链表之后,使用CAS将节点尾插入链表

    private Node enq(final Node node) {
    //死循环,在这里也就是自旋
    for (;;) {
        Node t = tail;
        if (t == null) { 
           //初始化当前链表,采用 lazy-load初始化,有头节点的双链表 头节点不存放数据
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            //再次采用CAS将节点尾插入链表中
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

将节点尾插入同步队列之后,调用accquireQueued(Node node, int arg)方法
节点在同步队列中排队获取同步状态

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)) {
                   //将当前节点设置为头节点
                    setHead(node);
                    //将前驱节点出队
                    p.next = null; 
                    failed = false;
                    return interrupted;
                }
                //此方法看下面
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //阻塞当前节点
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

if (p == head && tryAcquire(arg)) 在同步队列中,当前节点获取锁的前提–>只有当前驱节点为头节点线程才有机会获取同步状态

当前线程获取同步状态失败时,首先调用shouldParkAfterFailedAcquire(p,node)
方法作用:一直在自旋,尝试将前驱节点改为Node.SIGNAL,表示当前节点应该被阻塞!

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
   //获取前驱节点的状态
    int ws = pred.waitStatus;
    //如果前驱节点为SIGNAL,则表示当前节点应当被阻塞
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    //如果前驱节点状态值大于0,即表示为取消状态
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        //循环中一直找到当前节点的前驱节点不为SIGNAL状态
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
       //通过CAS方法将前驱节点状态设置为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
独占式锁的释放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值