【Java】---Lock体系与AQS详解

AQS-同步器

同步器是用来构建锁(Lock体系)以及其他同步组件的基础框架,他的实现主要依赖一个int状态变量以及通过一个FIFO队列共同构成同步队列。
子类必须重写AQS用protected修饰的来改变同步状态的方法,其他方法主要是实现排队与阻塞机制。int状态的更新使用getState()、setState()以及compareAndSetState()。
子类推荐使用静态内部类来继承AQS实现自己的同步语义。同步器支持独占锁,也支持共享锁。

Lock与AQS的关系:锁是面向使用者,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态的管理,线程的排队,等待和唤醒等底层操作
任何一个实现了Lock接口的类一定实现了一个静态内部类继承了AQS,通过AQS的方法实现同步语义。

AQS的模板设计模式
AQS使用模板方法模式,将一些与状态相关的核心方法开放给子类重写,而后AQS会使用子类重写的关于状态的方法进行线程

AQS提供的模板方法可以分为3类: 1. 独占式获取与释放同步状态; 2. 共享式获取与释放同步状态; 3. 查询同步队列中等待线程情况;

自己实现一个锁:

public class LockTest {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();
        for(int i=0;i<10;i++){
            Thread thread=new Thread(()->{
                try{
                    lock.lock();
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }finally {
                    lock.unlock();
                }
            });
            thread.start();
        }
    }
}

//自定义锁
class Mutex implements Lock{
    private Sync sync=new Sync();
    //AQS实现Lock的方法,自定义同步器
    static class  Sync extends AbstractQueuedSynchronizer {
        @Override
        //尝试获取锁
        protected boolean tryAcquire(int arg) {
           //0表示无锁
            if(arg!=1){
                throw new RuntimeException("arg参数不为1");
            }
            //CAS(0,1),若当前状态为0,则,用CAS将其置换为1,再把当前线程扔进去
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return  true;
            }

            return false;
        }

        @Override
        //释放共享
        protected boolean tryReleaseShared(int arg) {
           
            if(getState()==0){
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        //判断当前线程是否是持有线程
        protected boolean isHeldExclusively() {
            return getState()==1;
        }
    }
    
//----------------------------------------------------------------
    @Override
    public void lock() {
       //调用acquire上锁
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,time);
    }

    @Override
    public void unlock() {
sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return null;
    }
    //------------------------------------------------------------------
}

AQS详解

在同步组件中(Lock锁),AQS最核心的部分,同步组件的实现依赖AQS通过的模板方法来实现同步组件语义。

AQS实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等底层实现。

AQS核心组成:同步队列,独占锁的获取与释放,共享锁的获取与释放,可中断锁、超时锁。这一系列功能的实现依赖于AQS提供的模板方法。
独占锁的五个模板方法

  1. void acquire(int arg); 独占式获取同步状态,如果获取失败插入同步队列将进行等待。
  2. void acquireInterruptibly(int arg);此方法可以在同步队列中响应中断
  3. boolean tryAcqireNanos(int args,long nanosTimeOut);在2的基础上增加了超时功能,到了预计时间还未获得直接返回
  4. boolean tryAcquire(int arg);获取锁成功返回true,否则返回false
  5. boolean release(int arg);释放同步状态,该方法会唤醒在同步队列的下一个节点

共享式锁的模板方法:
1.void acquireShared(int arg);共享获取同步状态,同一时刻多个线程获取同步状态
2.void acquireSharedInterruptbly(int arg); 在1的基础上增加相应中断
3.boolean tryAcquireSharedNanos(int arg,long nanosTimeOut);在2的基础上增加超时等待
4.boolean releaseShared(int arg);共享式释放同步状态

同步队列

在AQS内部有一个静态内部类Node,这是同步队列中每个具体的节点.

节点中有如下属性:
int waitStatus:节点状态
Node prev:同步队列中的前驱节点
Node next:同步队列中的后继节点
Thread thread:当前节点包装的线程对象
Node nextWaiter:等待队列中的下一个节点

节点状态值如下:
int INTINL=0//初始状态
int CANCELLED=1//当前节点从同步队列中取消
int SIGNAL=-1; // 后继节点处于阻塞状态.如果当前节点释放同步状态会通知后继节点,使节点继续运行
int CONDITION=-2;//节点处于等待队列,其他线程对Condition调用signal方法后,该节点会从等待队列移到同步队列中
int PROPAGATE=-3//共享式状态会无条件的传播

AQS同步队列采用带有头尾节点的双向链表
【Java】---Lock体系与AQS详解_第1张图片

独占锁的获取:acquire(int arg);

获取锁失败后调用AQS提供的acquire(int arg)模板

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

【Java】---Lock体系与AQS详解_第2张图片
addWaiter()总结
线程获取失败,将线程调用addWaiter()封装成Node进行入队操作。addWaiter()中方法enq()完成对同步队列的头节点初始化以及CAS尾插失败后的重试处理

acquireQueued()总结:节点入队后排队获取同步状态
1.当前节点前驱为头节点并且调用tryAcquire()再次获取同步状态成功(前驱节点出队),当前线程获得锁成功
2.如果当前获取锁失败,先不断自旋将前驱节点状态置为SIGNAL,而后调用LockSupport.park()方法将当前线程阻塞

shouldParkAfterFailedAcquire(p,node)总结:节点在同步队列中获取锁,失败后调用shouldParkAfterFailedAcquire,此方法主要逻辑是使用CAS将前驱节点状态置为SIGNAL,表示需要将当前节点阻塞。如果CAS失败,不断自旋,直到前驱节点状态置为SIGANL为止。
parkAndCheckInterrupt():将当前节点调用LockSupport.park()阻塞

独占锁的释放:release()

unlock()方法实际调用AQS提供的release()模板方法
releas方法实现:是unlock()方法的具体实现,首先获取头节点的后继节点,当后继节点不为null,会调用LockSupport.unpark()方法唤醒后继节点包装的线程。因此每一次释放锁释放后就会唤醒队列中该节点的后继节点所包装的线程。

独占式锁的释放与获取的总结
获取:
1.线程获取失败,将线程调用addWaiter()封装成Node进行入队操作。addWaiter()中方法enq()完成对同步队列的头节点初始化以及CAS尾插失败后的重试处理。

2.入队之后排队获取锁的核心方法:acquireQueued(),节点排队获取锁是一个自旋过程。当且仅当当前节点的前驱节点为头节点并且成功获取同步状态时,节点出队并且该节点引用的线程获取到锁。否则,不满足条件时会不断自旋将前驱节点的状态设置为SIGNAL而后调用LockSupport.park()将当前线程阻塞。

释放:
释放锁时会唤醒后继节点(后继节点不为空)

独占锁特性
获取锁时响应中断。获取锁响应中断与acquire()几乎一样,唯一区别在于当parkAndCheckInterrupt()返回true时表示线程阻塞时被中断,抛出中断异常后线程退出。

超时等待获取锁
tryAcquireNanos(),该方法在三种情况下会返回:
1.在超时时间内,当前线程成功获取到锁。
2.当前线程在超时时间内被中断。
3.超时时间结束,仍未获取到锁,线程退出返回false

超时获取锁与可中断获取锁逻辑基本一致,唯一区别在于获取锁失败后,增加了一个时间处理,如果当前时间超过截取时间,线程不再等待,直接退出,返回false。否则将线程阻塞置为等待状态排队获取锁。

你可能感兴趣的:(JAVA)