共享式AQS

共享式AQS

阻塞式锁,在同一时刻只能有一个线程在执行,当一个线程执行完成后,再去释放下一个线程,而共享式是指,锁是可以被共享的,表现形式为,在同一时刻可以有多个线程运行。

通过源码分析共享式AQS的实现

CountDownLatch、Semaphore都属于共享锁。基于网上有好多博客都是分析CountDownLatch,所以我在此处分析一下Semaphore。

Semaphore的基本使用

public class SemaphoreTest {
        public static void main(String[] args) {
            Semaphore semaphore = new Semaphore(3);
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            semaphore.acquire();//获取锁
                            System.out.println(Thread.currentThread().getName() + " " + new Date() + " 我执行了");
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            semaphore.release();//释放锁
                        }
                    }
                }).start();
            }
        }
    }

上面的代码很简单,初始化一个可容纳3个线程的信号量,初始化10个线程,每个线程执行一次打印(延迟1秒)。
以上代码运行结果:

和排它锁相比,同一时刻允许多个线程同时执行。
看下Semaphore的内部类结构

从类结构上可以看出,Semaphore也是支持公平锁和非公平锁的实现,其锁的实现同样是通过Sync集成AQS实现。

Semaphore的acquire方法

public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

acquire sync通过调用AQS的acquireSharedInterruptibly方法实现

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())//首先判断线程是否被终端
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)//尝试获取锁,获取不到则执行下面方法,小于0代表获取不到锁,大于0代表获取成功,0代表成功,但没有剩余可用资源
        doAcquireSharedInterruptibly(arg);
}

cquireSharedInterruptibly名字可以看出这个方法是可相应中断的共享式获取锁。
tryAcquireShared肯定也是一个空方法,留给子类实现。

protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

看下Semaphore 公平锁的tryAcquireShared实现

protected int tryAcquireShared(int acquires) {
        for (;;) {//CAS的方式循环获取锁
            if (hasQueuedPredecessors())//队列中有线程在等待,直接返回-1,代表当前没有可用资源
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))//要么没有可用资源,要么当前资源CAS操作成功,则返回可用的资源数
                return remaining;
        }
    }

当没有可用的资源时,执行doAcquireSharedInterruptibly方法

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);//保证节点一定会被插入到队尾。
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//操作流程和排它锁一样
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

此时的数据结构如下:

主要看下setHeadAndPropagate方法,这个方法是在线程被唤醒之后要执行的

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())//节点为空(下面没有等待的线程)或者下一个节点依然是共享模式,那么就执从头结点开始执行唤醒
                doReleaseShared();
        }
    }

这种唤醒方式和排它锁的区别为,共享模式,每一次都是从头结点开始,唤醒一个线程,紧接着就会去试图唤醒下一个节点的线程,只要有足够的资源,线程就会被唤醒,就这样循环往复。而不像排它锁一样,要等待一个节点执行完成,再去唤醒下一个节点。

Semaphore的release方法

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//保证锁一定被释放
            doReleaseShared();//开启释放流程
            return true;
        }
        return false;
    }

共享锁总结

  1. 共享锁允许统一时刻有多个线程运行。
  2. 锁的释放过程和排他锁相比,具有传播性,一个节点被唤起,则会从头节点开始一个一个尝试唤醒。

你可能感兴趣的:(java,多线程)