【并发编程】(十三)JUC并发工具包的基础——AQS概述

文章目录

  • 1.AQS概念
    • 1.1.什么是AQS
    • 1.2.AQS的实现原理
  • 2.使用AQS实现互斥锁
    • 2.1.简单互斥锁实现
    • 2.2.可重入的实现

1.AQS概念

AQS是Java中的一个并发编程的框架,通过这个框架实现了一些并发编程中实用的功能。
本篇只会简单的提一下AQS的概念和使用方式,后续的笔记中会依据各个AQS实现类的源码来更深入的解析。

1.1.什么是AQS

AQS全称AbstractQueuedSynchronizer,顾名思义就是基于队列来实现的同步器框架,它可以实现线程之间的同步协作。我们经常会使用到的一些并发工具类都是基于它来实现的。
如重入锁ReentrantLock、读写锁ReentrantReadWriteLock、信号量SemaPhore、线程池ThreadPoolExecutor、计数器CountdownLatch、回环屏障CyclicBarrier

1.2.AQS的实现原理

AQS中维护了一个共享资源state状态字段和一个通过双向链表实现的FIFO队列。通过volatile来保证state和队列头尾节点的可见性,对state的修改及队列的头尾节点的变更通过CAS保证操作的原子性。

对于共享资源state的使用有独占和共享两种方式,独占的情况下,只有一个线程能对state做修改,共享则是所有线程都可以对state做修改。
有时候也会两种方式结合使用,如ReentrantReadWriteLock线程对写锁是独占的,对读锁是共享的,如果有一个线程持有了写锁,那其他的线程既不能获取到写锁也不能获取到读锁。

对于存在互斥的情况,就需要使用到FIFO队列了,没有抢占到共享资源的线程会进入到这个队列中等待,直到持有共享资源的线程运行结束时,才会唤醒队列头部的线程。

AQS的使用流程如下:
【并发编程】(十三)JUC并发工具包的基础——AQS概述_第1张图片

2.使用AQS实现互斥锁

2.1.简单互斥锁实现

上面提到了AQS是通过state和队列实现的,并且通过CAS实现原子性操作,我们可以先看一下在这个类中是如何定义的:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	
	// 共享资源sate
	private volatile int state;

	// 定义队列的头尾节点
    private transient volatile Node head;
    private transient volatile Node tail;

	// CAS实现对state变量的原子性操作
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

	// 尝试占用共享资源,失败则进入队列阻塞
    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;
    }
}

上面的AQS源码就是实现互斥锁的关键了,其中的tryAcquire()tryRelease()是模板方法,由实现类去自行实现加锁和解锁的判断。
也就是说,我们自定义一个互斥锁的话,只需要新建一个类继承AbstractQueuedSynchronizer然后重写这两个模板方法就可以了。

public class MyMutex extends AbstractQueuedSynchronizer {

    public void lock() {
        acquire(1);
    }

    public void unLock() {
        release(1);
    }

    @Override
    protected boolean tryAcquire(int arg) {
        return compareAndSetState(0, 1);
    }

    @Override
    protected boolean tryRelease(int arg) {
        // 只可能有一个线程持有锁,释放锁时不需要CAS
        setState(0);
        return true;
    }
}

几行代码就实现了一个最简单的互斥锁,但是这个简单的互斥锁没有实现可重入,容易导致死锁,如何实现可重入呢?

2.2.可重入的实现

可重入实现的关键是在于加锁是判断是否是当前线程在调用加锁的方法,需要在加锁时保存当前线程以作为后续的判断标识。

在上面的代码中可以看到,AQS还有一个父类AbstractOwnableSynchronizer

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
	
	// 定义保存当前线程的变量
    private transient Thread exclusiveOwnerThread;

	// 设置当前线程
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

	// 获取当前线程
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

然后对MyMutex中的模板方法的实现做一点修改:

@Override
protected boolean tryAcquire(int arg) {
    Thread currentThread = Thread.currentThread();
    // 抢占锁成功则设置当前线程为持有锁的线程
    if (compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(currentThread);
        return true;
    }
    // 抢占不成功时,判断是否被当前线程自己给占用了
    else if (currentThread == getExclusiveOwnerThread()) {
        // 重入次数 +1,用于后续依次解锁
        setState(getState() + arg);
        return true;
    }
    return false;
}

@Override
protected boolean tryRelease(int arg) {
    // 只可能有一个线程持有锁,释放锁时不需要CAS
    int state = getState();
    // 依次减少重入次数,当state为0时解锁成功
    int newState = state - arg;
    boolean unLockSuccess = false;
    if (newState == 0) {
        // 清除当前持有的线程
        setExclusiveOwnerThread(null);
        unLockSuccess = true;
    }
    setState(newState);
    return unLockSuccess;
}

以上就是重入锁的实现方式,只需要加入是否为当前线程的判断以及重入次数的正确增减就可以了。


在我们实际的使用中不需要自己再做互斥锁的封装,JUC包中提供了直接可以直接使用的锁工具类ReentrantLock,提供了上述的自定义互斥锁的所有功能,而且还支持公平锁、条件判断功能。
本篇没有对AQS中入队和出队的细节做分析,下一篇在分析ReentrantLock源码的时候,会带入这部分细节的分析。

你可能感兴趣的:(#,并发编程,Java,多线程,java,并发编程)