抽象队列同步器AQS应用Lock

  1. AQS特性

阻塞等待队列
共享/独占
公平/非公平
可重入
允许中断

  1. 锁的三大核心

自旋
lockSupport 来阻塞及唤醒
cas 算法 //比较与交换 unsafe 底层是依赖汇编指令 cmpxchg()
queue队列

AbstractQueuedSynchronizer
exclusiveOwnerThread 当前获取锁的线程是谁
aqs依赖一个整形变量state 为0 锁没被持有
队列是基于 内部类Node 双向链表 同步等待队列

可重入锁是state反复加1

  1. 多个线程竞争锁(公平锁)

获取当前线程
获取state 状态 ,如state为0 且 阻塞队列中没有其他线程则获取锁
exclusiveOwnerThread 指向当前线程
如state不为0,则判断当前持有锁的线程是不是自己,是自己则加1,如果不是自己持有的继续CAS。返回false,加锁失败。
加锁失败的线程直接加入队列

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;
}
  1. tryAcquire(arg) 锁竞争逻辑

acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 线程入队逻辑
队列中的Node 除了有共享,和独占之外的属性
waitState(整型) 标识当前节点的生命状态:信号量
sinnal =-1 可被唤醒
cancelled =1 出现异常 中断引起 需要被废弃
condition =-2 条件等待
propagate = -3 广播状态 传播
0 初始状态

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 入队操作

当线程入队时,位于head节点的线程会重新再抢一次锁
如果还没抢到锁,head节点才会把自己阻塞(此时第一轮循环把head节点置为sinnal=-1 代表可被唤醒。第二轮循环阻塞线程,并且判断线程是否有中断信号唤醒的),如果抢到锁则会直接出队列

注: head节点thread为null

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; // help GC
             failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire(p, node)
waitState = 0 ->-1 在持有锁的线程T0在释放锁之后,要判断head节点的waitState 是否!=0 ,如果waitState!=0 成立会再次waitState = 0 ->-1 。要想唤醒排队的第一个线程T1,T1被唤醒准备继续走循环拿锁。在非公平的情况下可能还会再次失败,此时可能T3拿到了锁!而T1会被再次阻塞,head节点会再次经历两次循环 waitState = 0 ->-1

Park阻塞线程唤醒的两种方式
中断
release()

线程被中断唤醒:抛异常
cancelAcquire(node);//标记当前节点为canceld,信号量,我们要剔除这样的节点

  1. 阻塞队列 BlockingQueue

线程通信的一个工具,在任意时刻、不管并发有多高,在单jvm上,同一时间永远只有一个线程能对队列进行出队或者入队操作
线程安全的队列

以ArrayBlockingQueue 为例

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair); //创建锁
    notEmpty = lock.newCondition(); //条件对象
    notFull =  lock.newCondition(); //条件对象
}

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();     //生产者加锁
    try {
        while (count == items.length) //队列满了
            notFull.await();     
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

//await可打开, 一进方法内,
1、先判断线程是否被中断,中断则抛出异常。
2、不被中断则会入Node队列(条件等待队列)。
3、通过锁状态释放锁。
4、通知CLH队列(竞争锁时线程的队列:同步队列)消费下一个节点。
注:线程获取的条件:只有在CLH队列里等待的node节点并且该节点的前一个结点是sinal,条件队列里的线程是不可以获取锁的。
注2:当有一个线程从queue队列中消费,留出一个空时。会发送一个sinal消息给条件队列这时候条件队列会转换成CLH队列

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值