暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【原创】JUC包源码分析07 | 借助AQS自定义锁

开发自由行 2021-06-29
204

文章导读:

【原创】JUC包源码分析01 | ArrayBlockingQueue

【原创】JUC包源码分析02 | LinkedBlockingQueue

【原创】JUC包源码分析03 | PriorityBlockingQueue

【原创】JUC包源码分析04 | SynchronousQueue

【原创】JUC包源码分析05 | AbstractQueuedSynchronizer

【原创】JUC包源码分析06 | AbstractQueuedSynchronizer


备注:JDK版本:1.8


AbstractQueuedSynchronizer作为Java并发包下的并发类设计的基石,熟悉AbstractQueuedSynchronizer的使用,能够让我们快速的开发一款适合自己业务的并发工具。

AbstractQueuedSynchronizer提供了用于获取、释放锁的模板方法,开发者只需要复写该模板方法里部分逻辑,既可以自实现锁。

acquire():获取独占锁

release():释放独占锁

acquireShared():获取共享锁

releaseShared():释放共享锁


    /** 
    * 获取一个独占锁,不考虑被干扰.
    */
    public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();



    /**
    * 释放独占锁,不考虑被干扰.
    */
    public final boolean release(int arg) {
    if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0)
    unparkSuccessor(h);
    return true;
    }
    return false;
    }


    /**
    * 获取一个共享锁,不考虑干扰.
    */
    public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
    doAcquireShared(arg);



    /**
    * 释放一个共享锁.
    */
    public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
    doReleaseShared();
    return true;
    }
    return false;
    }

    【原创】JUC包源码分析05 | AbstractQueuedSynchronizer小节中对上述4个方法的实现源码逻辑做了细致的介绍。其中的tryAcquire()、tryRelease()、tryAcquireShared()、tryReleaseShared()方法在AQS中均是未实现的方法,也就意味着这部分方法需要子类基于自身的业务场景实现。

    这样的设计是合乎情理的,AQS只是作为一个并发锁实现的基石类,它并不需要知道一个并发锁的业务实现,这也给JDK底层多种不同锁的实现提供了可行性,为业务侧自行基于业务场景扩展锁提供了更多的可能。


    本文将结合【原创】JUC包源码分析05 | AbstractQueuedSynchronizer的介绍,自定义一个独占(可重入)的锁,以加深对AQS底层设计的理解。

    既然是独占锁,那么在锁的内部,必然会复写AQS的tryAcquire()tryRelease()方法,具体的实现逻辑如下:

    我们自定义一个类,继承AQS,复写它的tryAcquire()和tryRelease()方法:

      /**
      * 复写AQS的tryAcquire()和tryRelease()方法
      */
      public class Sync extends AbstractQueuedSynchronizer {
      private static final long serialVersionUID = 0L;


      /**
      * 尝试获取锁方法
      *
      * @param acquires 量
      * @return 成功:true,失败:false
      */
      @Override
      protected boolean tryAcquire(int acquires) {
              // 获取当前线程
      final Thread current = Thread.currentThread();
      int c = getState();
              // 如果没有线程获取到锁
      if (c == 0) {
                  // CAS操作,为当前线程尝试获取锁
      if (compareAndSetState(0, acquires)) {
                      // 进入到这里,代表一定获取了锁
                      // 设置当前线程为独占锁线程
      setExclusiveOwnerThread(current);
                      // 返回true,代表获取了锁
      return true;
      }
              } // 判断是否是当前线程重复获取锁(锁重入)
      else if (current == getExclusiveOwnerThread()) {
                  // 进入到这里,代表是重入锁
                  // 设置state
      setState(c + acquires);
                  // 返回true,代表获取了锁
      return true;
      }
              // CAS失败,又不是重入,那么锁一定是被其他线程占用着
              // 即代表没有获取到锁
      return false;
      }


      /**
      * 尝试释放锁
      *
      * @param releases 量
      * @return 成功:true,失败:false
      */
      @Override
      protected boolean tryRelease(int releases) {
              // 直接将当前state减去相应的releases
      int c = getState() - releases;
              // 必须是当前线程才能释放锁
              // 也就是获取锁的线程才能释放自己的锁
      if (Thread.currentThread() != getExclusiveOwnerThread())
      throw new IllegalMonitorStateException();
      boolean free = false;
              // 如果c == 0, 代表之前的锁已经释放
      if (c == 0) {
                  // 此时没有线程持有锁
      free = true;
                  // 没有线程持有锁
      setExclusiveOwnerThread(null);
      }
              // 设置回state
      setState(c);
              // 返回true: 释放锁成功,没有线程持有锁
              // 返回false: 锁依然有线程在持有,可能是重入锁
      return free;
          }
      }

      上述代码中,我们自行实现了锁的获取和释放逻辑,并对该逻辑进行了详细的分析。当然,这部分逻辑是基于我个人的业务场景,并不适合所有的业务,该示例代码只是为了跟大家说明如何复写AQS的方法,为下文中锁的实现提供支撑。

        /**
         * Desp: 借用AQS实现可重入的独占锁
         * 2021-01-03 17:15 
        * Created by zhru.
        */
        public class SingleLock {


        /**
        * 复写AQS的tryAcquire()和tryRelease()方法
        */
         private static final class Sync extends AbstractQueuedSynchronizer{
        private static final long serialVersionUID = 0L;


        /**
        * 尝试获取锁方法
        *
        * @param acquires 量
        * @return 成功:true,失败:false
        */
        @Override
        protected boolean tryAcquire(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()) {
        setState(c + acquires);
        return true;
        }
        return false;
        }


        /**
        * 尝试释放锁
        *
        * @param releases 量
        * @return 成功:true,失败:false
        */
        @Override
        protected 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;
                }
        }


            // 定义变量
        private final Sync sync;


        /**
        * 默认构造方法
        */
        public SingleLock() {
        this.sync = new Sync();
        }


        /**
        * 尝试获取锁
        */
        public void tryLock() {
        // 直接调用我们自定义的tryAcquire()方法
        sync.tryAcquire(1);
        }


        /**
        * 获取锁
        */
        public void lock() {
                // 调用我们自定义AQS子类的acquire()方法
        sync.acquire(1);
        }


        /**
        * 释放锁
        */
            public void unlock() {
        // 调用我们自定义AQS子类的release()方法
        sync.release(1);
           }
        }

        从上面的代码中,我们可以发现,借助AQS,可以快速的设计一种适合自身业务场景的锁对象。在本示例中,用了不足100行代码,实现了一个可以重入的锁的核心逻辑。希望感兴趣的小伙伴,也能够主动借助AQS实现一种自己的锁,以加深对AQS的使用和理解。


        测试上述代码(以将一个值借用多线程不断的增加1为例):

          // 不使用锁的使用对象
          static int oneNum = 0;
          // 使用自定义锁时的使用对象
          static int twoNum = 0;
          // 循环次数
          static int roundSize = 100000;


          public static void main(String[] args) throws Exception {
             // 为了方便,直接调用Executors工具类
             // 实际业务场景,不建议使用
            ExecutorService executorService = Executors.newFixedThreadPool(5);


             for (int i = 0; i < roundSize; i++) {
          executorService.execute(() -> addOne());
          }
            
              // ===========使用自定义的锁============
          SingleLock singleLock = new SingleLock();
              for (int i = 0; i < roundSize; i++) {
          executorService.execute(() -> {
          singleLock.lock();
          try {
          addTwo();
          } finally {
          singleLock.unlock();
          }
          });
          }

          Thread.sleep(5000L);
          System.out.println("使用自定义锁执行结果:" + twoNum);
          executorService.shutdown();
          }


          // 自增1
          private static void addOne() {
          oneNum++;
          }
          // 自增1
          private static void addTwo() {
          twoNum++;
          }

          执行结果如下:

          上述结果表明未使用锁时,0自增100000次的结果明显小于100000。而使用我们自定义的锁时,上述计算过程从并行变成了串行,结果为100000,也间接证明了这点。


          因作者能力有限,文中可能出现描述不清的地方,不足之处请指出!希望与你一起共进步^

          文章转载自开发自由行,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论