zoukankan      html  css  js  c++  java
  • Java高并发连载23-基于AQS实现自定义同步器

    一、AQS-条件变量的支持(二)

    • 在如下代码中,当另外一个线程调用条件变量的signal方法的时候(必须先调用锁的lock方法获取锁),在内部会把条件队列里面队头的一个线程节点从条件队列里面移除并且放入AQS的阻塞队列里面,然后激活这个线程。
    public final void signal() {
     if(!isHeldExclusively()) {
      throw IllegalMonitorException();
     }
     Node first = firstWaiter;
     if(first != null){
      // 将条件队列头元素移动到AQS队列
      doSignal(first);
     }
    }
    • 需要注意的是,AQS提供了ConditionObject的实现,并没有提供newCondition函数,该函数用来new一个ConditionObject对象,需要由AQS的子类来提供newConditon函数
    • 下面来看当一个线程调用条件变量的await()方法而被阻塞后,如何将其放入条件队列
    private Node addConditionWaiter() {
     Node t = lastWaiter;
     ...
     // (1)
     Node node = new Node(Thread.currentThread(),Node.CONDITION);
     // (2)
     if(t == null){
      firstWaiter = node;
     }else {
      t.nextWaiter = node; // (3)
     }
     lastWaiter = node; // (4)
     return node;
    }
    • 代码(1)首先根据根据当前线程创建了一个类型为Node.CONDITION的节点,然后通过代码(2),(3),(4)在单向队列尾部插入一个元素
    • 注意:当多个线程同时调用lock.lock()方法获取锁时,只有一个线程获取到了锁,其他线程会被转换为Node节点插入到lock锁对应的AQS阻塞里面,并且做自旋CAS尝试获取锁
    • 如果获取到了锁的线程又调用对应条件变量的await()方法,则该线程会释放获取到的锁,并被转化为Node节点插入到条件变量对应的条件队列里面
    • 这时候因为调用lock.lock()方法被阻塞到AQS队列里面的一个线程会获取到被释放的锁,如果该线程也调用了条件变量的await()方法则该线程也会被放入条件变量的条件队列里面
    • 当另外一个线程调用条件变量的signal()或者signalAll()方法的时候,会把条件队列里面的一个或者全部Node节点移动到AQS的阻塞队列里面,等待时机获取锁。
    • 最后使用一个图总结:一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量有自己的一个条件队列。
    • 23.1
      23.1

    二、基于AQS实现自定义同步器

    • 基于AQS实现一个不可重入的锁,自定义AQS需要重写一系列的函数,还需要定义原子变量state的含义,在这里我们定义state为0表示目前锁没有被线程持有,state为1表示所已经被某一个线程持有,由于是不可重入锁,所以不需要记录持有锁的线程获取锁的次数,另外,我们自定义的锁支持条件变量。
    • 下面来看一下代码实现
    package com.ruigege.LockSourceAnalysis6;

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

    public class NonReentrantLockME implements Lock,java.io.Serializable{
     // 内部帮助类
     private static class Sync extends AbstractQueueSynchronizer {
      // 是否锁已经被持有
      protected boolean isHeldExclusively() {
       return getState() == 1;
      }
      
      // 如果state为0,则尝试获取锁
      public boolean tryAcquire(int acquires) {
       assert acquires == 1;
       if(compareAndSetState(0,1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
       }
       return false;
      }
      
      // 尝试释放锁,设置state为0
      protected boolean tryRelease(int release) {
       assert releases == 1;
       if(getState() == 0) {
        throw new IllegalMonitorStateException();
       }
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
      }
      
      // 提供条件变量接口
      Condition newConditon() {
       return new ConditionObject();
      }
     }
     
     // 创建一个Sync来做具体的工作
     private final Sync sync = new Sync();
     
     public void lock() {
      sync.acquire(1);
     }
     
     public boolean tryLock() {
      return sync.tryAcquire(1);
     }
     
     public void unlock() {
      sync.release(1);
      
     }
     public Condition newCondition() {
      return sync.newConditon();
     }
     
     public boolean isLocked() {
      return sync.isHeldExclusively();
     }
     
     public void lockInterruptibly() throws InterruptedException {
      sync.acquireInterruptibly(1);
     }

     public boolean tryLock(long timeout,TimeUnit unit) throws InterruptedException {
      return sync.tryAcquireNanos(1,unit.toNanos(timeout));
     }
    }

    • 如上面的代码,NonReentrantLock定义了一个内部类Sync用来实现具体的锁的操作,Sync则继承了AQS ,由于我们实现的独占模式的锁,所以Sync重写了tryAcquire ryRelease和isHeldExclusively3个方法,另外Sync提供了newCondition这个方法用来支持条件变量。

    三、源码:

  • 相关阅读:
    Linux -- 查看是否安装了指定的包
    linux -- 部署java服务器(1) linux安装jdk
    spring boot -- 接收文件接口
    vue3 --相对于vue2的改变T1档次
    243交换输出
    24416进制的简单运算
    7街区最短路径问题
    206矩形的个数
    33蛇形填数
    273字母小游戏
  • 原文地址:https://www.cnblogs.com/ruigege0000/p/14450174.html
Copyright © 2011-2022 走看看