7.并发编程---AQS介绍和源码剖析

什么是AQS

AQS全称为AbstractQueuedSynchronizer。其中的设计模板采用了,继承和模板方法设计模式。

其中常用的模板方法

独占式获取锁
1.accquire
2.acquireInterruptibly
3.tryAcquireNanos(超时获取)
共享式获取锁
1.acquireShared
2.acquireSharedInterruptibly
3.tryAcquireSharedNanos(超时获取)
独占式的释放锁
1.release
共享式释放锁
1.releaseShared

需要子类重写的方法

独占式锁、独占式释放锁、共享式释、共享式释放锁
tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared
其他需要重写的方法
isHeldExclusively(如果同步是独占的,则为true ;否则false)

同步状态的方法

getState
setState(此操作不具有原子性)
compareAndSetState(具有原子性的更新操作)

模板方法设计模式介绍

所谓的模板方法,就是父类中定义一个框架方法,具体每个流程有子类去实现。说白了就是父类是一个抽象类,父类中不确定的方法交给子类去实现。

/*
* 抽象类,演示短信发送的流程
* */
public abstract class SendCustom {
    //短信发给谁
    public abstract void to();
    //短信谁发的
    public abstract void from();
    //短信内容
    public abstract void content();
    //短信发送的类型
    public abstract void send();
    //发送时间是指当前时间,不需要子类去重写
    public void sendDate(){
        System.out.println("短信发送时间:"+new Date());
    }

    //发送短信的模板方法,是不需要子类去重写的。直接调用即可
    public void senMessgae(){
        from();
        to();
        sendDate();
        content();
        send();
    }
}

class SendMsg extends SendCustom{

    @Override
    public void to() {
        System.out.println("ms");
    }

    @Override
    public void from() {
        System.out.println("yt");
    }

    @Override
    public void content() {
        System.out.println("短信发送的内容");
    }

    @Override
    public void send() {
        System.out.println("发送的是邮件");
    }

    public static void main(String[] args) {
        SendMsg sendMsg = new SendMsg();
        sendMsg.senMessgae();
    }
}

自己实现一个独占式Lock锁

实现一个独占锁,必须要实现Lock接口,并且用内部类继承AbstractQueuedSynchronizer重写其isHeldExclusively、tryAcquire、tryRelease方法

/*
 * 自己实现一个类似于ReentrantLock独占式锁功能
 * 独占锁需要重写:tryAcquire、tryRelease,isHeldExclusively(
 * */

/**
 * @ClassName: SelfLock
 * @Description:实现一个独占锁
 */
public class SelfLock implements Lock {
    private Sync sync = new Sync();

    //重写aqs
    static class Sync extends AbstractQueuedSynchronizer {
        //获取锁
        @Override
        protected boolean tryAcquire(int arg) {
            //如果state==0时候,说明锁空闲
            if (compareAndSetState(0, 1)) {
                //蒋当前线程设置成可访问锁权限
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //释放锁
        @Override
        protected boolean tryRelease(int arg) {
            //如果状态为1时候,进行释放state=0
            if (compareAndSetState(1, 0)) {
                //蒋当前线程访问锁权限释放
                setExclusiveOwnerThread(null);
                return true;
            }
            return false;
        }

        //独占锁返回true
        @Override
        protected boolean isHeldExclusively() {
            //如果当前锁已经被占用返回true
            return getState() == 1;
        }

        final ConditionObject conditionObject() {
            return new ConditionObject();
        }
    }


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

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

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toMillis(time));
    }

    @Override
    public Condition newCondition() {
        return sync.conditionObject();
    }
}

class DemoTest {
    private SelfLock selfLock = new SelfLock();

    public void testSync() {
        selfLock.lock();
        try {
            SleepUtils.sleepSeconds(1);
            System.out.println(Thread.currentThread().getName() + "执行中.....");
        } finally {
            selfLock.unlock();
        }
    }

    public static void main(String[] args) {
        DemoTest demoTest = new DemoTest();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                demoTest.testSync();
            }).start();
        }
    }
}
Thread-0---执行中
Thread-1---执行中
Thread-2---执行中
Thread-3---执行中
Thread-4---执行中
Thread-5---执行中
Thread-6---执行中
Thread-7---执行中
Thread-8---执行中
Thread-9---执行中

查看输出结果,可知道selfLockMethods方法锁已经起效了,每次只有一个线程可以拿到锁

AQS独占式数据结构-节点+同步队列

节点+同步队列
在这里插入图片描述

简单解释就是说:java在多线程的情况下,其他线程拿不到锁,那么这些线程会被打包成Node然后放进同步队列中。

Node节点状态解释

  /*线程等待超时或者被中断了,需要从队列中移除掉*/
  static final int CANCELLED =  1;
  /*后续的节点等待的状态,当前的节点去通知后面的节点去运行 */
  static final int SIGNAL    = -1;
  /*当前的节点处理等待队列 */
  static final int CONDITION = -2;
  /*共享,表示状态要想后面的节点进行传播,0表示初始状态*/
  static final int PROPAGATE = -3;

上面所有状态值,都赋值与 volatile int waitStatus;

抢夺锁流程

在这里插入图片描述
在这里插入图片描述

看到上面两个红框,是不是对尾节点采用CAS设置,而首节点不需要感觉疑惑?前面已经说到了一个列子,就是抢夺锁可以是多个线程一起来抢,但释放锁,缺只有一个线程。同样道理,比如线程A、B、C、D四个线程。A已经获取到锁,那么B、C、D线程应该进入同步队列进行等待,但要是BCD三个线程同时执行,这时候,BCD到底谁先在队尾呢?针对这个情况,采用CAS可以避免。

抢夺锁源码分析

在上面我们自己实现的一个SelfLock锁有一个Lock重写方法,查看acquire方法


    @Override
    public void lock() {
        //加锁=独占锁
        sync.acquire(1);
    }
   public final void acquire(int arg) {
   //如果线程没有获取到锁的话,就为当前线程创建节点,并且加入等待队列末尾进行等待
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
private Node addWaiter(Node mode) {
	  Node node = new Node(Thread.currentThread(), mode);
     // tail等待队列的尾部,仅通过方法 enq 修改以添加新的等待节点。
     Node pred = tail;
     if (pred != null) {
         node.prev = pred;
         //采用cas方式设置尾部节点,将当前节点,赋值给尾部节点。成功返回当前节点。设置失败会调用enq方法
         if (compareAndSetTail(pred, node)) {
             pred.next = node;
             return node;
         }
     }
     enq(node);
     return node;
    }
 private Node enq(final Node node) {
 //采用自旋的方式,进行CAS设置头部
   for (;;) {
   		//默认是尾节点
        Node t = tail;
        //如果没有未节点,说明它是头节点
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
        	//有尾部节点(t),将尾节点(t)赋值给当前节点(node)头节点(node.prev)返回尾节点
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
    }
/*以独占不间断模式获取已在队列中的线程。由条件等待方法和获取使用。
*/
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);
        }
    }

其中的tryAcquire()是我们SelfLock重写的获取锁的方法

acquire方法里面的意思就是说:如果当前线程已经获取到锁,那么直接返回,如果当前线程未获取到锁,那么会调用addWaiter方法为尝试使用CAS操作给节点设置在队列的尾节点,如果失败了会调用enq方法进行自旋方式,将当前节点插入队列的尾部中。

锁释放锁源码分析

在上面我们自己实现的锁SelfLock有一个tryRelease方法

 @Override
    public void unlock() {
        sync.release(1);
    }
public final boolean release(int arg) {
	//如果锁已经释放了
     if (tryRelease(arg)) {
     	//赋值头节点
         Node h = head;
         if (h != null && h.waitStatus != 0)
         	//唤醒节点的后继者
             unparkSuccessor(h);
         return true;
     }
     return false;
}
 private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);	
         //获取头节点的后面的一个节点进行唤醒
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

Condition的等待队列

在这里插入图片描述
在这里插入图片描述

ReentrantLock源码分析

作用:可重入锁、公平和非公平锁。
ReentrantLock的基本实现可以概括为:先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:
非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;
公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。

非公平锁加锁

//在ReentrantLock源码212行有非公平锁尝试获取锁的方式
  protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

点击查看nonfairTryAcquire方法


     final boolean nonfairTryAcquire(int acquires) {
     	//获取当前线程
         final Thread current = Thread.currentThread();
         //获取锁的状态,0-锁是被释放,1-锁占用
         int c = getState();
         //如果锁被释放,那么就立马枪到当前锁,不进入同步队列进行等待,这是与非公平锁的本质上的区别
         if (c == 0) {
         	//cas进行状态state设置成1成功后
             if (compareAndSetState(0, acquires)) {
             	//将当前线程设置成独占访问锁的权限
                 setExclusiveOwnerThread(current);
                 return true;
             }
         }
         //如果锁被占用了,并且正好是当前线程占用的话
         else if (current == getExclusiveOwnerThread()) {
         	//对占用锁状态进行累加操作赋值给state
             int nextc = c + acquires;
             if (nextc < 0) // overflow
                 throw new Error("Maximum lock count exceeded");
               //赋值给state
             setState(nextc);
             return true;
         }
         return false;
     }

可重入锁加锁源码其实理解起来很简单,就是判断获取到锁的是不是当前的线程,如果是当前线程的话,进行状态state累加操作,然后通过释放锁的时候,进行自减直到state=0在释放锁。

公平锁加锁

	//代码在231行
  protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //如果当前锁是释放状态
        if (c == 0) {
        	//hasQueuedPredecessors方法是与非公平锁的主要区别,看下面的源码
            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;
    }

hasQueuedPredecessors源码,大概意思就是说,如果当前线程有前驱节点,就添加到同步队列的尾部进行排队获取锁,没有前驱节点,那自己当作首节点。

 public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

释放锁

  protected final boolean tryRelease(int releases) {
  			//将state-锁的次数赋值给c
          int c = getState() - releases;
          if (Thread.currentThread() != getExclusiveOwnerThread())
              throw new IllegalMonitorStateException();
          boolean free = false;
          //持有锁是当前线程,并且c=0表示锁已经全部释放结束后,
          if (c == 0) {
              free = true;
              //将线程访问锁的权限释放
              setExclusiveOwnerThread(null);
          }
          //state=0,表示锁未被使用
          setState(c);
          return free;
      }

公平与非公平锁区别

通过上面源码分析,发现两者本质上的区别就是。非公平锁是属于抢占式锁,只要当前锁是释放的状态,那么等待的线程就可以直接枪到该锁。而公平锁即使在当前锁是释放状态,依然会先去同步队列中判断下是否有前驱节点,如果有的话就将自己加入到尾部,进行等待前驱其他线程执行结束。这样有一个问题就是,在同步队列中的锁会被先挂起然后在被唤醒这一个过程是消耗时间的。而非公平锁缺不存在这个消耗,所以说非公平锁的效率要大于公平锁。

读写锁源码分析

参考:https://www.csdn.net/tags/MtTaYg5sODQ3MzEtYmxvZwO0O0OO0O0O.html
读写锁还存在一个锁降级的概念,就是在释放写锁之前可以将锁降级为读锁,但是读锁不可升级为写锁。锁降级的目的一直都存在争议,但其实锁降级就是一种特殊的可重入操作。存在一直情况,如果当前线程已经持有写锁,但如果当前线程再来获取读锁的话应该是允许的,但如果写锁不释放的话,就算是本身线程来获取读锁也要进行等待,这样是不合理的,所以说它允许锁降级为读锁,这样就可以不用等待直接获取读锁了,也是为了效率的一种体现。

protected final int11 tryAcquireShared(int unused) {

    Thread current = Thread.currentThread();
    int c = getState();
    // exclusiveCount(c) != 0 ---》 用 state & 65535 得到低 16 位的值。如果不是0,说明写锁别持有了。
    // getExclusiveOwnerThread() != current----> 不是当前线程
    // 如果写锁被霸占了,且持有线程不是当前线程,返回 false,加入队列。获取写锁失败。
    // 反之,如果持有写锁的是当前线程,就可以继续获取读锁了。
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        // 获取锁失败
        return -1;
    // 如果写锁没有被霸占,则将高16位移到低16位。
    int r = sharedCount(c);// c >>> 16
    // !readerShouldBlock() 和写锁的逻辑一样(根据公平与否策略和队列是否含有等待节点)
    // 不能大于 65535,且 CAS 修改成功
    if (!readerShouldBlock() && r < 65535 && compareAndSetState(c, c + 65536)) {
        // 如果读锁是空闲的, 获取锁成功。
        if (r == 0) {
            // 将当前线程设置为第一个读锁线程
            firstReader = current;
            // 计数器为1
            firstReaderHoldCount = 1;

        }// 如果读锁不是空闲的,且第一个读线程是当前线程。获取锁成功。
         else if (firstReader == current) {// 
            // 将计数器加一
            firstReaderHoldCount++;
        } else {// 如果不是第一个线程,获取锁成功。
            // cachedHoldCounter 代表的是最后一个获取读锁的线程的计数器。
            HoldCounter rh = cachedHoldCounter;
            // 如果最后一个线程计数器是 null 或者不是当前线程,那么就新建一个 HoldCounter 对象
            if (rh == null || rh.tid != getThreadId(current))
                // 给当前线程新建一个 HoldCounter ------>详见下图get方法
                cachedHoldCounter = rh = readHolds.get();
            // 如果不是 null,且 count 是 0,就将上个线程的 HoldCounter 覆盖本地的。
            else if (rh.count == 0)
                readHolds.set(rh);
            // 对 count 加一
            rh.count++;
        }
        return 1;
    }
    // 死循环获取读锁。包含锁降级策略。
    return fullTryAcquireShared(current);
}

在这里插入图片描述

共享锁源码分析

共享锁与独占锁的主要区别就是在于state,独占锁的state默认在0(锁被释放),1(锁被使用)之间来回切换。而共享锁,它的state必须是>1的数。比如共享锁的state=10,说明可以有十个线程共享这个锁,并且每个线程得到锁后state-1,如果state=0时候,其他在想拿锁的线程,这个时候会进入同步队列进行等待被唤醒,当线程使用完锁会将state+1,这时候会唤醒同步队列中的等待的线程。

自己实现一个共享锁

/*
 * 实现一个共享锁,并且该锁,可以被指定数量线程同时拿到锁
 * */
public class ShareLock implements Lock {
    //默认可以同时三个线程拿锁
    private final Sync sync = new Sync(3);

    public ShareLock() throws IllegalAccessException {
    }

    static class Sync extends AbstractQueuedSynchronizer {

        //赋值默认的初始state,其值表示可以被多少个线程同时拿锁使用
        public Sync(int count) throws IllegalAccessException {
            if (count <= 0) {
                throw new IllegalAccessException("count must large then zero");
            }
            setState(count);
        }

		 /*
       * 获取锁的时候,将state-1;当state<0时候,说明这时候已经没有共享线程已经被使用完。
       * 其他线程需要拿锁,需要进入同步队列中等待,源码的放已经明确说明了。当扣减完后,doAcquireShared将线程放入队列
       *   public final void acquireShared(int arg) {
               if (tryAcquireShared(arg) < 0)
                   doAcquireShared(arg);
             }

       * */
        @Override
        protected int tryAcquireShared(int arg) {
            //自旋
            for (; ; ) {
                //获取当前状态
                int crrentState = getState();
                //获取到锁的时候,将当前state-arg,一般情况一个线程-1
                int newState = crrentState - arg;
                //如果锁获取成功或者此时state<0时候,返回最新状态
                if (newState < 0 || compareAndSetState(crrentState, newState) ) {
                    return newState;
                }
            }
        }

        /*
         *
         * 释放锁原理就是没释放一个锁,state+1,表示空余出来的位置,可以被其他线程进行使用
         * */
        @Override
        protected boolean tryReleaseShared(int arg) {
            for (; ; ) {
                int crrentSate = getState();
                //一般情一个线程释放锁state-1
                int newSate = crrentSate + arg;
                if (compareAndSetState(crrentSate, newSate)) {
                    return true;
                }
            }
        }

        final ConditionObject conditionObject() {
            return new ConditionObject();
        }

    }

    @Override
    public void lock() {
        sync.acquireShared(1);
    }

    @Override
    public void unlock() {
        sync.releaseShared(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquireShared(1) >= 0;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
    }

    @Override
    public Condition newCondition() {
        return sync.conditionObject();
    }
}

class TestDemo7 {
    private ShareLock shareLock = new ShareLock();


    TestDemo7() throws IllegalAccessException {
    }

    public void test1() {
        shareLock.lock();
        try {
            SleepTools.sencod(1);
            System.out.println(Thread.currentThread().getName() + "正在访问test1方法");
        } finally {
            shareLock.unlock();
        }
    }

    public static void main(String[] args) throws IllegalAccessException {
        TestDemo7 testDemo7 = new TestDemo7();
        //观察输出结果,没三组输出一次
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                testDemo7.test1();
            }).start();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AQS(AbstractQueuedSynchronizer)是Java并发编程中的一个重要类,它可以理解为抽象的队列同步器。AQS提供了一种基于FIFO队列的同步机制,用于实现各种同步器,如ReentrantLock、CountDownLatch、Semaphore等。 AQS的核心思想是使用一个volatile的int类型变量state来表示同步状态,通过CAS(Compare and Swap)操作来实现对state的原子更新。AQS内部维护了一个双向链表,用于保存等待获取同步状态的线程。 AQS的具体实现包括以下几个方面: 1. 内部属性:AQS内部有两个重要的属性,一个是head,表示队列的头节点;另一个是tail,表示队列的尾节点。 2. 入队操作:AQS的入队操作是通过enq方法实现的。在入队操作中,首先判断队列是否为空,如果为空,则需要初始化队列;否则,将新节点添加到队列的尾部,并更新tail指针。 3. CAS操作:AQS的CAS操作是通过compareAndSetHead和compareAndSetTail方法实现的。这些方法使用CAS操作来更新head和tail指针,保证操作的原子性。 4. 出队操作:AQS的出队操作是通过deq方法实现的。在出队操作中,首先判断队列是否为空,如果为空,则返回null;否则,将头节点出队,并更新head指针。 5. 同步状态的获取和释放:AQS提供了acquire和release方法来获取和释放同步状态。acquire方法用于获取同步状态,如果获取失败,则会将当前线程加入到等待队列中;release方法用于释放同步状态,并唤醒等待队列中的线程。 通过继承AQS类,可以实现自定义的同步器。具体的实现方式是重写AQS的几个关键方法,如tryAcquire、tryRelease等,来实现对同步状态的获取和释放。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值