从零开始java多线程并发---锁(七):AQS的共享锁功能的实现(从CountDownLatch分析)

一:共享锁

  java中一般定义写锁为独享锁而读锁为共享锁,而java中读锁单独存在是没有意义的,原因如下:

  • 读锁只是对资源的读取没有对资源的修改,那么效率会远小于没有锁的读。

 java中只要读锁是依赖于写锁的,不同于写锁的所操作:获取锁然后操作(获取没有获取到锁进入等待阻赛队列)。读锁一般都是直接调用wait()方法进入线程阻赛的。其原因是,读锁一定保证线程中的写锁(至少是当前线程中的写锁)执行完毕。也就是说在一组读写锁中,写锁的优先级要大于读锁的。那么,总结一下读写锁的过程区别:

  • 写锁:获取到锁,执行代码,释放锁,后续队列中的线程继续(或者获取不到锁,进入队列阻赛等待)
  • 读锁:进入阻赛,等待所有写锁执行完毕,然后所有的线索同时被通知执行。

 

二:共享锁的过程

 这里模拟一个比较复杂的读写锁过程吧:

  • 独享锁A获取到锁,进入代码执行
  • 独享线程B,C,D发现锁被占用,只好到FIFO队列等待被唤醒
  • 共享锁E查询锁状态,若是锁没有被占用执行查询,否则继续执行一下步骤
  • 共享锁E发现线程被占用,进入FIFO,并且发现前面有线程C,D
  • 贡献锁线程F,G执行E的步骤

执行线程锁释放功能

  • 线程A释放锁,通知B,B执行完毕通知C
  • C执行完毕,通知E,E这时候联结F,G准备执行
  • E,F,G同时执行

三:共享锁源码分析

  •   共享锁源码分析,直接从线程wait()执行,这里的获取锁等方法就不讲了
  •   这里使用Synchronized单独对读方法枷锁模拟,解析CountDownLatch

(1)结构

从零开始java多线程并发---锁(七):AQS的共享锁功能的实现(从CountDownLatch分析)_第1张图片

(2)CountDownLatch(int)


//实例化,同时锁存器+1
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

//实现AQS的一个子类,初始化同步构造器
 private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }
...
}

 (2)await()

//线程等待
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

//尝试获取到锁
  public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
            
        //响应中断
        if (Thread.interrupted())
            throw new InterruptedException();
        
        //获取到锁(否则加入等待队列,至于如何封装成节点,加入FIF的就不用说了)
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

当同步状态state为0时,表示当前为无锁状态,处于await状态下的线程都可以执行。在读写锁的方法中,只有写锁能够修改state的状态值(毕竟读锁状态下只提供一个初始化的构造方法)。一旦写锁释放了锁,那么在处于等待队列的读锁都可以运行,直到下一个写锁方法再次上锁。

 (3)countDown()

//释放锁存器
public void countDown() {
        sync.releaseShared(1);
    }
    
//尝试对锁存器操作
   public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

这是调度线程之间关系的方法:如在一个读写方法中存在这样一个等待队列:写读读读读写。那么这个锁存器就记录了当前写到下一个写之间的所有读的个数,一旦当前写释放锁,通知后续的个数的读同时进行。相当于一个通知的功能。

四:功能简述

  上面主要的方法讲述完成,下面我们来简要的说一下读写锁下的加锁/解锁方式:

  • 写锁加锁执行
  • 读锁调用构造方法,锁存器+1,进入等待队列,并响应中断(。。。)
  • 写锁执行完毕,通知队列后的锁存器中个数的读锁进行执行,同时重置锁存器个数

写锁的核心就是:运行到写锁时,方法变成无锁方法。

 

你可能感兴趣的:(AQS共享功能,并发锁)