AQS+ReentrankLock

AQS+ReentrankLock

介绍

什么是AQS?

AQS是JUC包下的一个同步队列器,他是大部分并发工具类的核心。

AQS的作用

AQS的主要作用是使用state去控制资源数,并使用队列去维护没有获取到资源的线程。他使用模版方法设计模式把操作资源的方法交给子类实现,其中最典型的实现就是ReentantLock。

AQS的应用场景

线程协作
Semaphore、CountDownLatch、(CyclicBarrier没有,内部使用lock好像**)

线程安全
ReentrantReadWriteLock、ReentrantLock

线程管理
ThreadPoolExecutor

源码分析

AQS结构

AbstractOwnableSynchronizer(AOS)

核心属性和方法

Java
Thread exclusiveOwnerThread; // 独占模式资源持有者(线程)

void setExclusiveOwnerThread(Threadthread); // set方法

Thread getExclusiveOwnerThread(); // get方法

核心属性和方法

Java
int WAITING = 1; // 常量1,表示线程等待

int CANCELLED = 0x80000000; // 常量负数,表示取消等待

int COND = 2; // 和等待队列有关

Node head;// 同步队列的头结点,空节点,不包含线程(特殊)

Node tail;// 同步队列的尾节点

int state;// 同步状态,state==1,放入同步队列中

class Node{ // 通过Node实现了同步队列,双向链表

Node prev; // 链表中的prev,volatile修饰

Node next; // 链表中的next,volatile修饰

Thread waiter;        // Node包装的线程

int status; // 原子位操作,相当于状态,volatile修饰

Node SHARED = new Node(); // 共享模式

int CANCELLED =  1; // 标识线程已处于结束状态,表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化

int SIGNAL    = -1; // 标识线程已处于可被唤醒状态

int CONDITION = -2; // 标识线程已处于条件状态,表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

int PROPAGATE = -3; // 与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态

int waitStatus; // 同步队列中结点状态:CANCELLED、SIGNAL、CONDITION、PROPAGATE 4种, waitStatus表示的是后续结点状态,这是因为AQS中使用CLH队列实现线程的结构管理,而CLH结构正是用前一结点某一属性表示当前结点的状态,这样更容易实现取消和超时功能。

Node nextWaiter; // 等待队列中的后继结点,这个与Condition的等待队列有关

}

class ConditionObject{}

tryAcquire(int arg) //尝试获取独占锁-模版方法(待重写)

tryRelease(int arg) //尝试释放独占锁-模版方法(待重写)

tryAcquireShared(int arg) //尝试获取共享锁-模版方法(待重写)

tryReleaseShared(int arg) //尝试释放共享锁-模版方法(待重写)

isHeldExclusively() //判断是否为持有独占锁(待重写)

setState();//没有竞争时使用,效率高一些,一般是持有锁的线程

getState();//获取状态

compareAndSetState();//由竞争的时候使用

acquire();

acquireShared() ;

acquireInterruptibly() ;

acquireSharedInterruptibly() ;

release() ;

releaseShared() ;

addWaiter();

addWaiter()

Java
private Node addWaiter(Node mode) { // 创建新节点,并加入队列

// 将请求同步状态失败的线程封装成结点

Node node = new Node(Thread.currentThread(), mode);

 // 获取尾节点

Node pred = tail;

// 如果尾节点为null,说明头结点都还没初始化

if (pred != null) {

    // 如果头结点被初始化,就直接把新节点加入队列(快速插入)

    node.prev = pred;

    //使用CAS执行尾部结点替换,尝试在尾部快速添加,如果CAS失败,执行enq

    if (compareAndSetTail(pred, node)) {

        //为什么CAS会失败呢?因为这时候可能有多个线程包装成的结点放入到队尾

        pred.next = node;

        return node;

    }

}

// 把新节点加入队列,头结点不存在就初始化头结点

enq(node);

return node;

}

enq()

Java

private Node enq(final Node node) {

for (;;) {

    Node t = tail;

    // 如果尾节点为null,说明队列中没有节点,开始初始化头结点

    if (t == null) { 

            // 新建一个空节点作为头结点

        if (compareAndSetHead(new Node()))

                // 尾指针指向头结点

            tail = head;

    } else {

        node.prev = t;

        if (compareAndSetTail(t, node)) {

            t.next = node;

            return t;

        }

    }

}

}

acquireQueued()

TypeScript
final boolean acquireQueued(final Node node, int arg) {

boolean failed = true;

try {

    boolean interrupted = false;

    // 出循环的两种情况1、获取到锁 2、被中断?????说明新节点在队列中不会被阻塞?直到获取锁?

    for (;;) {

        // 获取新节点的前置节点

        final Node p = node.predecessor();

        // 如果是头结点,就尝试获取锁

        if (p == head && tryAcquire(arg)) {

            // 获取锁成功,把当前节点设为头结点,并置为null

            setHead(node);

            p.next = null; // help GC

            failed = false;

            return interrupted;

        }

        // 判断挂起的两种情况

        // 1、前驱结点不是head;

        // 2、前驱节点是head,但是获取锁失败

        if (shouldParkAfterFailedAcquire(p, node) 

            && parkAndCheckInterrupt()) // 这里面有个阻塞方法LockSupport.park(this);

            interrupted = true;

    }

} finally {

    if (failed)

        //最终都没能获取同步状态,结束该线程的请求,并退出队列

        cancelAcquire(node);

}

}

shouldParkAfterFailedAcquire()

TypeScript
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

// pred是node的前驱节点,但是pred不是头节点,因为如果是头节点,node就不用挂起了

// 获取前驱节点的等待状态

int ws = pred.waitStatus;

// 等待唤醒状态(SIGNAL)

if (ws == Node.SIGNAL){

    return true;

}

//如果ws>0 则说明是结束状态,遍历前驱结点直到找到不是结束状态的结点



if (ws > 0) 

{

    // 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。

// 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!

    do 

    {

        node.prev = pred = pred.prev;

    } 

    while (pred.waitStatus > 0);

    pred.next = node;

} 

// ws小于0又不是SIGNAL状态

// 一般是从等待队列中刚转过来,是Condition状态 则将其设置为SIGNAL状态,代表该结点的线程正在等待唤醒

else 

{

    //  //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!

    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

}

// 整个流程中,如果前驱结点的状态不是SIGNAL,那么自己就不能安心去休息,需要去找个安心的休息点,同时可以再尝试下看有没有机会轮到自己拿号。

return false;

}

parkAndCheckInterrupt()

Java
//只有当前驱节点是唤醒状态,才挂起当前结点

private final boolean parkAndCheckInterrupt() {

//将当前线程挂起

LockSupport.park(this);

//获取线程中断状态,interrupted()是判断当前中断状态

//并非中断线程,因此可能true也可能false,返回

return Thread.interrupted();

}

问说有哪些属性,首先链表必定有头指针和尾指针,方便操作,还有节点,节点内部有值和前后指针,AQS最核心的就是state

ReentrankLock

介绍

什么是ReentrankLock?

ReentrankLock是基于AQS实现的可重入锁,有公平和非公平两种实现,其中非公平的效率比较高,比较常用。

ReentrankLock的作用是什么?

ReentrankLock的主要作用就是锁住临界区,来保证共享变量的安全(让线程在一定程度上变得有序)。

ReentrankLock应用场景

适合写多读少的需要保证共享变量安全的情况

ReentrankLock源码分析

ReentrankLock结构

核心属性和方法

Java
Sync sync;//

abstract static class Sync extends AbstractQueuedSynchronizer {

abstract void lock();//抽象方法,子类实现

boolean nonfairTryAcquire(int acquires);//获取非公平锁,不写在非公平队列里面是因为                 tryLock需要调用他

boolean tryRelease(int releases);//释放锁

boolean isHeldExclusively();//持有锁线程是否是当前线程

int getHoldCount(); // 获取重入次数

boolean isLocked(); // 判断是否被锁住

ConditionObject newCondition() ;//创建Condition队列

}

static final class NonfairSync extends Sync {

void lock();

boolean tryAcquire(int acquires); //获取锁

}

static final class FairSync extends Sync {

void lock();

boolean tryAcquire(int acquires); //获取锁

}

lock、trylock、lockInterruptibly对比

TypeScript
//-------------------------------------------- 1.lock-非公平-起点 ------------------------------------------------------------

// if (compareAndSetState(0, 1)){ -------------------------

// setExclusiveOwnerThread(Thread.currentThread());   | //非公平锁独有

// } ----------------------------------------------------

//-------------------------------------------- 2.lockInterruptibly-公平和非公平-起点 -------------------------------------------

// if (Thread.interrupted()){---------------

 // throw new InterruptedException();  | //中断锁独有

// }----------------------------------------

//-------------------------------------------- 3.lock-公平-起点 --------------------------------------------------------------

//-------------------------------------------- 4.tryLock-起点 ---------------------------------------------------------------

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

// hasQueuedPredecessors公平锁独有,try没有这部分

if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {

    setExclusiveOwnerThread(current);

}

}

else if (current == getExclusiveOwnerThread()) {

int nextc = c + acquires;

if (nextc < 0)

    throw new Error("Maximum lock count exceeded");

setState(nextc);

}

//------------------------------------------ 4.tryLock-终点 -----------------------------------------------------------------

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);

        // throw new InterruptedException() // 中断锁独有

}

//------------------------------------------ 1.lock-非公平-终点 -------------------------------------------------------------

//------------------------------------------ 2.lockInterruptibly-公平和非公平-终点 --------------------------------------------

//------------------------------------------ 3.lock-公平-终点 ---------------------------------------------------------------

面试题

ReentrankLock是悲观锁还是乐观锁,为什么?

lock锁局部使用的是乐观锁,总体使用的是悲观锁。

乐观锁是指不需要获取锁,直接进行操作

悲观锁是指必须要获取到锁,才能进行操作

lock锁底层使用cas去获取锁(state),所以说他局部是乐观锁

但是总整体角度上看,必须获取到lock锁,才能进入到临界区进行操作,所以说整体是一个悲观锁。

ReentrankLock和Synchronized的区别?

相同点,都是悲观锁

不同点

1、使用方式不同,一个是关键字,一个是类,关键字内部自己实现了上锁和解锁的逻辑,类必须主要调用方法进行上锁和解锁,但是灵活性会更高

2、功能不同,lock可以响应中断,可以判断是否获取到锁,有公平和非公平的实现,还可以顺序唤醒等待队列中的线程。Synchronized不可以响应中断,只有非公平实现,唤醒等待队列中的线程也是随机的。

3、底层原理不同,ReentrankLock是基于AQS,使用cas+locksupport实现的,Synchronized是基于cas+对象头+monitor实现的。

ReentrankLock常用方法有哪些?有什么区别?

常用方法有lock、中断lock、tryLock。

他们的核心逻辑都是一样的,都会去判断锁是否被持有,没有被持有就用cas尝试去获取锁。如果锁被人持有,就会判断持有线程是否是当前线程,是的话就重入。这个这三种方法相同的逻辑。

不同的是,lock和中断lock在没获取到锁之后,都会新增节点,进入阻塞队列。而tryLock会直接返回一个布尔值。

重点是lock和中断lock的区别。

lock和lockInterruptibly有什么区别?为什么lockInterruptibly可以被中断?

他们都是锁,不过中断锁再没获取到锁阻塞的时候,可以打断阻塞,并抛出中断异常,退出阻塞队列。lock锁也可以响应中断,因为底层使用的都是lockSupport,但是他只会给一个中断标记,然后继续循环,继续阻塞。

在非公平的情况下,中断锁还比lock锁少了一次获取锁。公平锁情况下基本是一致的。

谈一谈你对AQS的理解

AQS是JUC包下的一个同步队列器,他是大部分并发工具类的核心。他的主要作用是使用state去控制资源数,并使用队列去维护没有获取到资源的线程。他使用模版方法设计模式把操作资源的方法交给子类实现,其中最典型的实现就是ReentantLock。

哪些并发工具类是通过AQS实现的?

根据线程协作,有Semaphore和CountDownLatch

根据线程安全,有ReentrantLock和ReentrantReadWriteLock

根据线程管理,有线程池

JUC下都有哪些并发工具类?

Atomic原子类,并发集合类,并发队列,线程协作类,线程池

读写锁,也分为公平和非公平

state拆两半,低16位,用来记录写锁,高16,读,代表重入次数

拆两半不用两个变量是因为无法用一次CAS 同时操作两个int变量

state=0说明没有读锁也没有写锁,如果>0就判断是读锁还是写锁

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值