【二十一】Java多线程J.U.C之AQS框架源码导读(总结、干货)(模板模式template pattern)

版权声明:转载注明出处 https://blog.csdn.net/jy02268879/article/details/86298531

一、AQS的模板模式解读

AbstractQueuedSynchronizer该类使用的设计模式是模板模式template pattern。

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

在AbstractQueuedSynchronizer类中,封装不可变部分的方法前都加了final,子类不能覆盖,比如:

    protected final int getState() {
        return state;
    }

    protected final void setState(int newState) {
        state = newState;
    }

而行为由父类控制,子类实现的方法为这5个:

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
   protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }

 AbstractQueuedSynchronizer类并没有实现这5个方法,而是直接抛出异常,需要它的子类去扩展

AQS的子类这里以ReentrantLock中的Sync类为例

下图:蓝色实心箭头表示继承关系,红色线条表示内部类

Sync继承了AQS类,并实现了tryRelease和isHeldExclusively方法。

而AQS类的tryAcquire方法由Sync的两个子类,FairSync(公平锁)和NoFairSync(非公平锁)分别去实现 ,不同的 加锁方式。

AQS类的另外两个模板方法tryAcquireShared和tryReleaseShared,在ReentrantLock中不会实现,因为ReentrantLock是排它锁,不用实现共享锁的方法。

以调用ReentrantLock的公平锁的lock方法为例,用图表达:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行这句话在代码中的提现:

 AQS类的acquire方法定义了获取锁的整体逻辑,但是其中的tryAcquire逻辑由子类实现,不同的子类有不同的实现。

子类(FairSync)在获取锁的时候用的父类(AQS)的acquire方法,而父类(AQS)的acquire方法在执行获取锁的逻辑的时候运行的部分逻辑tryAcquire是调用的该子类重写了的逻辑。

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

二、三大核心

1.AQS维护了一个volatile int state共享资源

    /**
     * The synchronization state.
     */
    private volatile int state;

AQS使用一个int类型的成员变量state来表示同步状态,state=0表示同步状态可用(如果用于锁,则表示锁可用),state=1表示同步状态已被占用(锁被占用),在重入锁的情况下可以state>1。

AQS提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作。

    protected final int getState() {
        return state;
    }

    protected final void setState(int newState) {
        state = newState;
    }

    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

2.AQS维护了一个FIFO双向链线程等待队列

多线程争用资源被阻塞时会进入该队列。

源码中Node类保存着线程引用和线程状态的容器,它是同步队列中的每一个节点。

属性名称 描述
int waitStatus 表示节点的状态。其中包含的状态有:
  1. CANCELLED,值为1,表示当前的线程被取消;
  2. SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
  3. CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
  4. PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
  5. 值为0,表示当前节点在sync队列中,等待着获取锁。
Node prev 前驱节点
Node next 后继节点
Node nextWaiter 存储condition队列中的后继节点
Thread thread 入队线程

debug调试AQS入队出队示例

在main中起3个线程。Thread-3最先执行,Thread-1,Thread-3一次排队。

代码:

package com.sid;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @program: thread-test
 * @description:
 * @author: Sid
 * @date: 2019-01-14 14:11
 * @since: 1.0
 **/
public class AQSTest {

    public static int threadTotal = 3;
    public static int count = 0;
    private final static Lock lock = new ReentrantLock(true);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < threadTotal ; i++) {
            executorService.execute(new Runnable() {
                public void run() {
                    try {
                        doSomeThing();
                    } catch (Exception e) {
                        System.out.println(e);
                    }
                }
            });
        }
        executorService.shutdown();
    }

    private static void doSomeThing() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

调试步骤及解说:

1.Thread-3先获取锁lock.lock(),一直不要释放,不要运行finally中的lock.unlock()。

此时AQS中

int state=1

Thread exclusiveOwnerThread=Thread-3

2.Thread-1去获取锁,获取不到,进入AQS的Sync Queue同步队列,并且park挂起。

第一次进入同步队列的时候会触发创建一个不封装线程的head头节点

private Node addWaiter(Node mode) {
    ...
    //该方法触发创建一个不封装线程的head节点
    enq(node);
}
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

3.Thread-2去获取锁,获取不到,进入AQS的Sync Queue同步队列,排在Thread-1的node后面,并且park挂起。

4.等 Thread-1,Thread-2都进入同步队列等待之后,放开Thread-3的端点,让Thread-3走到finally中的lock.unlock()方法中。

 Thread-3释放资源(tryRelease(arg)方法)的后,AQS类的int state=0  并且 Thread exclusiveOwnerThread=null。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

如果当天Sync Queue同步队列中,head头节点不为空,并且头节点的等待状态waitStatus!=0,

那么把头节点head的等待状态waitStatus设置为0,并且unpark唤醒head.next节点中的线程。这里即是唤醒了Thread-1线程。

 Thread-1线程被唤醒后,继续执行AQS中acquireQueued方法。

如果它的prev前驱节点是head头节点,那么执行tryAcquire(arg)获取锁。

获取锁成功后会把自己变成head头节点,把自己封装的线程设置为空,并且把原来的头节点移除队列。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    //获取锁成功后会把自己变成head头节点
                    setHead(node);
                    //把原来的头节点移除队列
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //睡去的入口,被唤醒的出口
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

此时的Sync Queue变成了:

 5.Thread-1线程执行完成后,调用lock.unlock()方法释放资源,唤醒Sync Queue的下一个线程Thread-2。

与上面唤醒Thread-1线程的逻辑雷同,这里就只贴出唤醒Thread-2时,同步队列Sync Queue的变化

变成了

3.AQS中维护了ConditionObject队列

一个Lock可以创建多个Condition。

Condition对象只能用于独占模式。

ConditionObject使用相同的内部队列节点,但是维护在一个单独的单项条件队列中,也是链表,当Sync Queue队列中的node执行到await的时候,节点会转移到Condition Queue中。当收到signal操作的时候将Condition Queue条件队列的节点转移到Sync Queue等待队列中。 

在main中期4个线程,thread-1,thread-2,thread-3,thread-4。

代码:

package com.sid;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @program: thread-test
 * @description:
 * @author: Sid
 * @date: 2019-01-14 17:13
 * @since: 1.0
 **/

public class ConditionTest {
    private final static Lock lock = new ReentrantLock(true);

    public static void main(String[] args) {
        Condition condition1 = lock.newCondition();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("thread-1 "+" do ");
                } finally {
                    lock.unlock();
                }
            }
        });
        t1.setName("Thread-1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    waitCondition1( condition1,"Thread-2","condition1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.setName("Thread-2");
        t2.start();

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    waitCondition1( condition1,"Thread-3","condition1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t3.setName("Thread-3");
        t3.start();

        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("Thread-4 get lock");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                condition1.signal();
                System.out.println("Thread-4 send condition1 signal ~ ");
                lock.unlock();
            }
        });
        t4.setName("Thread-4");
        t4.start();

    }

    private static void waitCondition1(Condition condition,String threadName,String conditionName) throws InterruptedException {
        lock.lock();
        try {
            System.out.println(threadName+" wait "+conditionName+" signal");
            condition.await();
            System.out.println(threadName+" get "+conditionName+" signal");
        } finally {
            lock.unlock();
        }
    }
}

步骤:

1.thread-1 lock.lock()获取锁后,不要释放锁,不要执行unlock

2.thread-2 开始执行,获取不到锁,进入Sync Queue队列中等待。

3.thread-3 开始执行,获取不到锁,进入Sync Queue队列中等待。

4.thread-4 开始执行,获取不到锁,进入Sync Queue队列中等待。

5. thread-1执行lock.unlock()释放锁,thread-2将被唤醒去竞争锁。

线程2在Sync Queue中被unpark唤醒,获取到锁。

将head指向自己,将自己node封装的thread=null。

把以前的head node出列。

执行后面的业务逻辑到condition.await()

进入Condition Queue队列,

释放锁,将waitStatus=0,

unpark唤醒Sync Queue中Next Node,即是Node@825 thread-3

park睡眠自己。

6.thread-3被唤醒,将去竞争锁

线程3在Sync Queue中被unpark唤醒,获取到锁。

将head指向自己,将自己node封装的thread=null.

把以前的head node 即是Node@641出列。

执行后面的业务逻辑到condition.await()

进入Condition Queue队列

释放锁,将waitStatus=0

unpark唤醒Sync Queue中Next Node,即是Node@928 thread-4

park睡眠自己

 

 7.thread-4将被唤醒,去竞争锁

线程4在Sync Queue中被unpark唤醒,获取到锁。

将head指向自己,将自己node封装的thread=null。

把以前的head node 即是Node@825出列。

执行后面的业务逻辑到condition.signal()。

将Condition Queue中的fistWaiter即是Node@1104 thread-2出列

把fistWaiter指向1104的nextWaiter 即是Node@1243 thread-3.

将1104的nextWaiter=null,waitStatus=0.

将1104的进入Sync Queue队列的末尾。

Node@928的waitStatus=-1.

Sync Queue中的Node@928 thread-4执行完逻辑后释放锁

将Node@928的waitStatus=0

unpark唤醒Sync Queue中 928的next Node 即是Node@1104 thread-2

三、两种资源共享方式

1.Exclusive

独占锁。只要AQS的state变量不为0,并且持有锁的线程不是当前线程,那么代表资源不可访问。

比如ReetrantLock就是用的这种方式。

2.Share

共享锁。只要AQS的state变量不为0,那么代表资源不可以为访问。

比如CountDownLatch就是用的这种方式。它使一个或多个线程等待一组事件发生。

闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。

执行countDown方法递减计数器,表示有一个事件已经发生,

await方法等待计数器达到零,表示所有需要等待的时间都已经发生。

如果计数器值非零,那么await会一直阻塞直到计数器为零,或者等待线程中断,或者等待超时。

猜你喜欢

转载自blog.csdn.net/jy02268879/article/details/86298531