当前位置:网站首页>aqs的学习

aqs的学习

2022-04-23 19:47:00 Fairy要carry

目录

概述:

需要由子类实现的保护方法

 其他方法

锁和同步器的区别 

自定义锁-独占锁

AQS小结


概述:

通过继承它来实现父类的功能

获取锁:

阻塞和获取资源用的是park和unpark;

释放锁:

判断是否有锁资源:

  

 AbstractQueuedSynchronizer公有方法(AQS)

1.acquire:独占式获取同步状态;

2.release:独占式释放同步状态;

3.acquireShared(int arg):

共享式获取同步状态,如果当前线程未获取到同步状态,那么就进队列等待——>与独占式的主要区别是在同一时刻可以有多个线程获取同步状态;

4.releaseShared(int arg):共享式释放同步状态;

需要由子类实现的保护方法

 1.tryAcquire:独占式获取同步状态;——通过cas操作设置锁资源占有状态,并且对锁的Owner进行设置

 2.tryRelease:独占式释放同步状态;

 3.getState:返回锁状态

还有几个shared

 其他方法

锁和同步器的区别 

锁:面向使用者,定义了锁和使用者交互方式 ,隐藏了细节;

同步器:面向的是锁的实现者,简化了锁的实现方式;

锁和同步器很好的隔离了使用者和实现着所需要关注的领域;


自定义锁-独占锁

package com.example.juc.AQS;

import lombok.extern.slf4j.Slf4j;

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

import static java.lang.Thread.sleep;

/**
 * @author diao 2022/4/23
 */
@Slf4j(topic = "c.TestAqs")
public class TestAqs {
    public static void main(String[] args) {
        MyLock lock = new MyLock();
        new Thread(()->{
           lock.lock();
           log.debug("locking...");
           lock.lock();//synchronized和ReentrantLock都是可重入
            try {
                log.debug("locking...");
                sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        },"t1").start();

//      new Thread(()->{
//          lock.lock();
//          try {
//              log.debug("locking...");
//          } finally {
//              log.debug("unlocking...");
//              lock.unlock();
//          }
//      },"t2").start();
    }
}

//自定义锁(不可重入锁);可重入锁:拿到锁资源下次进入一样可以进入
class MyLock implements Lock{

    //独占锁,AQS基于队列(同步器)
    class MySync extends AbstractQueuedSynchronizer{

        /*1.尝试获取锁资源*/
      @Override
        protected boolean tryAcquire(int arg) {
            //因为可能会有多个线程竞争锁资源,所以要进行cas
            if(compareAndSetState(0,1)){
                //尝试将锁的owner设置为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /*2.尝试释放锁资源*/
        @Override
        protected boolean tryRelease(int arg) {
            //这里需要注意顺序,setState有volatile字段
            // 有读写屏障,前面的修改锁拥有资源对其他线程都可见
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        /*3.判断是否持有独占锁*/
        @Override
        protected boolean isHeldExclusively() {
            return getState()==1;
        }

        public Condition newCondition(){
            return new ConditionObject();
        }
    }

    private MySync sync=new MySync();

    @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中的tryRelease是不一样的
         * 上面的只是改变锁的拥有者以及锁的state,但并不会唤醒被阻塞的线程
         * release可唤醒阻塞线程
         */
        sync.release(1);
    }

    @Override//返回条件变量
    public Condition newCondition() {
        return sync.newCondition();
    }
}

 如代码所示,独占锁实现了在同一时刻只能用一个线程获取到锁,而其他获取锁的线程只能在等待队列等待;


AQS小结

 使用方式?

AQS主要是通过继承的方式,子类通过继承同步器并实现它的抽象方法->来管理同步状态;

如何管理同步状态?

AQS使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。

特点:

AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程;

站在使用角度,我理解其实就两个功能:独占和共享,要么独占要么共享,然后调用底层api;

AQS的大致实现思路

AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁(自旋),条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
 

版权声明
本文为[Fairy要carry]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_57128596/article/details/124364671