• JAVA并发-基于AQS实现自己的显示锁


    一、了解什么是AQS

    原文链接:http://www.studyshare.cn/blog/details/1131/1

    AQS是AbstractQueuedSynchronizer (抽象队列同步器)的简称java中近一半的显示锁是基于AQS实现的。例如:ReentrantLock(独占锁)、Semaphore(信号量)、ReentrantReadWriteLock(读写锁)、CountDownLatch(并发工具类)、ThreadPoolExecutor(线程池)

     AQS原理:

    1.采用双向链表的数据结构,当多线程同时竞争锁的时候,第一个线程拿到锁后,后续的线程封装成Node节点依次进入同步队列进行排队等待。

    2.AQS内部会采取自旋(死循环)的机制,一直判断头节点是否满足获取锁的条件,当锁被第一个线程释放后,队列中头节点条件满足(检查锁的状态是否为0),然后让头节点获取到锁,并脱离队列,如下图:

    AQS原理其实并不复杂,就是采用一个同步队列来存储节点(对线程进行包装,node中有thread,prev,next等),并自旋判断头节点是否拿到锁,拿到锁就从队列中移除,如果有新的线程进入则加到队列的尾部。

    java开发工具下载地址及安装教程大全,点这里。   更多技术好文,在这里

    二、AQS采用的设计模式及主要方法

    (1)AQS采用的是模板方法模式,对模板方法模式不清楚请参考:https://www.cnblogs.com/stonefeng/p/5743673.html

    (2)模板方法

        独占式获取

             acquire()

             acquireInterrutpibly()

             tryAcquireNanos()

            

             共享式获取

             acquireShared()

             acquireSharedInterruptibly()

             tryAcquireSharedNanos()

             独占式释放

             release()

             共享式释放

             releaseShared()

            

             需要覆盖的流程方法

             独占式获取

             tryAcquire()

            

             共享式获取

             tryAcquireShared()

             独占式释放

             tryRelease()

             共享式释放

             tryReleaseShared()

             isHeldExclusively() :该方法返回同步状态,需要自己覆盖实现

      以上方法是AQS的几个核心方法,在下面我们分析源码的时候会具体解释

    三、实现一个案例:继承AQS实现一个自己的独占锁

    //实现Lock接口
    public class MyReentrantLock implements Lock {

    //使用state做锁的状态标志,state=1表示获取到锁,state=0表示释放锁,其他线程可以竞争锁
    private static class Sync extends AbstractQueuedSynchronizer{

    /**
    * 尝试获取锁的方法
    * 需要自己实现的流程方法
    * @param arg
    * @return
    */
    @Override
    protected boolean tryAcquire(int arg) {
    //CAS比较内存中的原始值为0,则修改为传入的状态值1,当前线程获取到锁
    if(compareAndSetState(0 , arg)){
    setExclusiveOwnerThread(Thread.currentThread());//当前线程得到了锁,则将当前得到锁的线程设置为独占线程
    return true;
    }
    return false;
    }

    /**
    * 释放锁的方法,需要实现
    * @param arg
    * @return
    */
    @Override
    protected boolean tryRelease(int arg) {
    if(getState() == 0){//判断状态是否为0,为0则直接抛出不支持操作的异常,增强健壮性的代码
    throw new UnsupportedOperationException();
    }
    setExclusiveOwnerThread(null);//将当前独占线程设置为null
    setState(0);//将当前标志锁状态的值设置为0,表示锁已经释放
    return true;
    }

    /**
    * 是否同步独占,true--已被独占,false--未被独占
    * @return
    */
    @Override
    protected boolean isHeldExclusively() {
    return getState() == 1 ;
    }

    Condition newCondition(){
    return new ConditionObject();//AQS已经实现Condition,此处只需要直接实例化并使用AQS中的实现即可
    }
    }

    private Sync sync = new Sync();

    @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.release(1) ;//释放锁
    }

    @Override
    public Condition newCondition() {
    return sync.newCondition();//获取AQS中的Condition实例,用于等待、唤醒操作
    }
    }

    1、显示锁的实现机制是实现Lock接口,使用内部类去继承AQS,为何这样做其实就是java是单继承的,AQS是抽象类,Lock是接口,使用接口更具有扩展性。
    2、需要自己覆盖的流程方法
      tryAcquire():获取锁
      tryRelease():释放锁
      isHeldExclusively():是否同步独占,true--已被独占,false--未被独占(根据state状态值判断)
    测试类:
    public class LockTest {
    static MyReentrantLock lock = new MyReentrantLock();
    public static void main(String[] args) {
    for(int i=0;i<5;i++){
    Thread threadA = new Thread(new Runnable() {
    @Override
    public void run() {
    lock.lock();
    try{
    System.out.println("获取到锁处理业务逻辑");
    Thread.sleep(1000) ;
    } catch (InterruptedException e) {
    e.printStackTrace();
    } finally{
    lock.unlock();
    System.out.println("释放锁");
    }
    }
    });
    threadA.start();
    }
    }
    }
    四、深入源码,解读AQS原理
    1、多线程并发获取锁,lock.lock(),该方法调用后会执行 sync.acquire(1); 进入这个方法的源码

             

      tryAcquire()就是我们自己覆盖实现的方法,尝试去获取锁,得到则返回true,没得到则返回false,如果返回true,则acquire()则直接执行完毕,不会继续往下执行,如果返回false,说明没有得到锁,则需要将当前线程加入等待队列中。addWaiter()就是加入等待队列的方法,进入源码:

          

      接着看看enq()方法的源码:

            

      以上就成功将一个需要等待的线程封装成节点类后加入等待队列了。如果有更多的线程需要加入,则enq(队列还没有任何节点)方法不需要执行,在addWaiter()中代码实现了直接往尾节点后面继续加入。这里是入队列的代码实现,接着看看出队列的具体实现

      

      该方法实现出队列,看源码:

      

      

      以上方法就走完了获取锁的流程,执行到这一步,在头节点的线程其实是处于阻塞状态的,它需要等待当前持有锁的那个线程去唤醒它,才会继续执行自己的业务代码

      接着看看释放锁的源码实现:

      

      

      总结:到这里其实整个AQS的流程就已经走完了,AQS是使用双向链表数据机构实现一个入队和出队的操作(先进先出),新增节点挂在尾节点的后面,自旋判断当前节点的前驱节点是否是头节点,如果是头节点并尝试获取锁,获取成功就将头节点和后面的节点脱钩,并处于阻塞状态,等待释放锁的方法去唤醒。

      以上代码只讲解了AQS实现了一个基本的显示锁,如果要实现可重入锁或者读写锁,需要加入其它控制和实现其它方法,在此就不讨论,有兴趣可以自己去看可重入锁和读写锁的实现。

    java开发工具下载地址及安装教程大全,点这里。   

    更多技术好文,在这里

  • 相关阅读:
    awk应用
    字符串应用,expect预期交互,数组,正则表达式
    for,while循环,case分支,shell函数
    数值运算,if结构
    shell基础应用,变量的扩展应用
    rsync基本用法与配置,split分离解析
    PXE自动装机
    配置DNS服务器
    进程查看,终止
    应用技巧,vim用法,编译安装软件包
  • 原文地址:https://www.cnblogs.com/darendu/p/9890566.html
Copyright © 2020-2023  润新知