aqs的学习

目录

概述:

需要由子类实现的保护方法

 其他方法

锁和同步器的区别 

自定义锁-独占锁

像我们的ReentrantLock就是由AQS同步器实现的

unlock解锁流程

AQS小结



概述:

通过继承它来实现父类的功能

获取锁:

阻塞和获取资源用的是park和unpark;

释放锁:

判断是否有锁资源:

  

 AbstractQueuedSynchronizer公有方法(AQS)

1.acquire:独占式获取同步状态;

2.release:独占式释放同步状态;

3.acquireShared(int arg):

共享式获取同步状态,如果当前线程未获取到同步状态,那么就进队列等待——>与独占式的主要区别是在同一时刻可以有多个线程获取同步状态;

4.releaseShared(int arg):共享式释放同步状态;

需要由子类实现的保护方法

 1.tryAcquire:独占式获取同步状态;——通过cas操作设置锁资源占有状态,并且对锁的Owner进行设置

 2.tryRelease:独占式释放同步状态;

 3.getState:返回锁状态

还有几个shared

 其他方法

锁和同步器的区别 

锁:面向使用者,定义了锁和使用者交互方式 ,隐藏了细节;

同步器:面向的是锁的实现者,简化了锁的实现方式;

锁和同步器很好的隔离了使用者和实现着所需要关注的领域;


自定义锁-独占锁

package com.example.juc.AQS;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import static java.lang.Thread.sleep;

/**
 * @author diao 2022/4/23
 */
@Slf4j(topic = "c.TestAqs")
public class TestAqs {
    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(()->{
           lock.lock();
           log.debug("locking...");
           lock.lock();//synchronized和ReentrantLock都是可重入
            try {
                log.debug("locking...");
                sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t1").start();

//      new Thread(()->{
//          lock.lock();
//          try {
//              log.debug("locking...");
//          } finally {
//              log.debug("unlocking...");
//              lock.unlock();
//          }
//      },"t2").start();
    }
}

//自定义锁(不可重入锁);可重入锁:拿到锁资源下次进入一样可以进入
class MyLock implements Lock{

    //独占锁,AQS基于队列(同步器)
    class MySync extends AbstractQueuedSynchronizer{

        /*1.尝试获取锁资源*/
      @Override
        protected boolean tryAcquire(int arg) {
            //因为可能会有多个线程竞争锁资源,所以要进行cas
            if(compareAndSetState(0,1)){
                //尝试将锁的owner设置为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /*2.尝试释放锁资源*/
        @Override
        protected boolean tryRelease(int arg) {
            //这里需要注意顺序,setState有volatile字段
            // 有读写屏障,前面的修改锁拥有资源对其他线程都可见
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        /*3.判断是否持有独占锁*/
        @Override
        protected boolean isHeldExclusively() {
            return getState()==1;
        }

        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    private MySync sync=new MySync();

    @Override//加锁(不成功,会进入等待队列)
    public void lock() {
        sync.acquire(1);
    }

    @Override//加锁,可打断
    public void lockInterruptibly() throws InterruptedException {
         sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override//尝试加锁,带超时
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,unit.toNanos(time));
    }

    @Override//解锁
    public void unlock() {
        /**
         * 和sync中的tryRelease是不一样的
         * 上面的只是改变锁的拥有者以及锁的state,但并不会唤醒被阻塞的线程
         * release可唤醒阻塞线程
         */
        sync.release(1);
    }

    @Override//返回条件变量
    public Condition newCondition() {
        return sync.newCondition();
    }
}

 如代码所示,独占锁实现了在同一时刻只能用一个线程获取到锁,而其他获取锁的线程只能在等待队列等待;

 源码分析

 然后我们看看release方法源码

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

然后看看unparkSuccessor方法 ,会发现实际上核心就是unpark方法

 private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

像我们的ReentrantLock就是由AQS同步器实现的

比如说lock()方法——>实际上是调用了同步器的lock方法,然后调用里面的acquire()方法(阻塞和非阻塞)

1.ReentranLock中的lock() 

  public void lock() {
        sync.acquire(1);
    }

 2.然后进入同步器中的acquire方法

2.1如果tryAcquire()加锁成功或者重入成功,后面的就不用执行了(将节点封装node传入阻塞队列)

2.2 如果没拿到锁为false,就会进入后面的操作

//以独占模式获取,忽略中断。通过调用至少一次 tryAcquire 来实现,成功返回。否则线程排队,可能///重复阻塞和解除阻塞,调用 tryAcquire 直到成功。此方法可用于实现方法 Lock.lock。参数:arg -// 获取参数。这个值被传递给 tryAcquire 但不会被解释并且可以代表你喜欢的任何东西
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 2.2阻塞队列,线程没拿到锁的情况(AQS提供的)

第一个节点总是空的,后面的才是带线程的,入队等待

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

        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                initializeSyncQueue();
            }
        }
    }

 3.然后我们看看AQS同步器中的tryAcquire(),这个是需要Reentranlock实现增强的,就是cas操作设置0,1状态,然后将拥有这设置为当前线程返回true

  protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

3.1如果是不公平锁实现的

   static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

 会进行状态state的判断,如果值不为0说明锁就被占用了,如果为0说明占有成功返回true,加锁成功后会记录当前线程是有锁线程(两次)——>state!=0说明锁已被占用,判断当前线程身上是否有锁,如果有则重入(0+1,体现出了两次判断——>没拿到锁可能自己身上有锁资源)

加锁成功或者重入成功都返回true

 @ReservedStackAccess
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                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;
        }

4.然后我们看看下面的acquireQueued()方法,目的就是封装为一个node节点传入阻塞队列中

unlock解锁流程

1.本质上就AQS的release方法,以独占模式发布。如果 tryRelease 返回 true,则通过解锁一个或多个线程来实现。此方法可用于实现方法 Lock.unlock。参数:arg - 释放参数。这个值被传递给 tryRelease,但不会被解释并且可以代表任何你喜欢的东西。返回:从 tryRelease 返回的值

  public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

2.然后点进tryRelease()方法

protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

然后看看它在ReentranLock中的实现

对state的值进行-1然后判断——>1.判断减后是否为0,为0解锁成功返回true,并且将当前拥有线程设置为null;2.如果不为0就为false

 @ReservedStackAccess
        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);
            }
            setState(c);
            return free;
        }

AQS小结

 使用方式?

AQS主要是通过继承的方式,子类通过继承同步器并实现它的抽象方法->来管理同步状态;

如何管理同步状态?

AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。

特点:

AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程;

站在使用角度,我理解其实就两个功能:独占和共享,要么独占要么共享,然后调用底层api;

AQS的大致实现思路

AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁(自旋),条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fairy要carry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值