基于 AbstractQueuedSynchronizer 的并发类实现

164人阅读 评论(0) 收藏 举报

公平模式ReentrantLock实现原理

前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQueuedSynchronizer的并发类是如何实现的。

ReentrantLock显然是一种独占锁,首先是公平模式的ReentrantLock,Sync是ReentractLock中的基础类,继承自AbstractQueuedSynchronizer,看一下代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;
 
    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock();
 
    /**
     * Performs non-fair tryLock.  tryAcquire is
     * implemented in subclasses, but both need nonfair
     * try for trylock method.
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
 
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
 
    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
 
    final ConditionObject newCondition() {
        return new ConditionObject();
    }
 
    // Methods relayed from outer class
 
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
 
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }
 
    final boolean isLocked() {
        return getState() != 0;
    }
 
    /**
     * Reconstitutes this lock instance from a stream.
     * @param s the stream
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

Sync属于一个公共类,它是抽象的说明Sync会被继承,简单整理一下Sync主要做了哪些事(因为Sync不是ReentrantLock公平锁的关键):

  1. 定义了一个lock方法让子类去实现,我们平时之所以能调用ReentrantLock的lock()方法,就是因为Sync定义了它
  2. 实现了非公平锁tryAcquira的方法
  3. 实现了tryRelease方法,比较简单,状态-1,独占锁的线程置空
  4. 实现了isHeldExclusively方法
  5. 定义了newCondition方法,让开发者可以利用Condition实现通知/等待

接着,看一下公平锁的实现,FairSync类,它继承自Sync:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
 
    final void lock() {
        acquire(1);
    }
 
    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

整理一下要点:

1. 每次acquire的时候,state+1,如果当前线程lock()之后又lock()了,state不断+1,相应的unlock()的时候state-1,直到将state减到0为之,说明当前线程释放完所有的状态,其它线程可以竞争

2. state=0的时候,通过hasQueuedPredecessors方法做一次判断,hasQueuedPredecessors的实现为”h != t && ((s = h.next) == null || s.thread != Thread.currentThread());”,其中h是head、t是tail,由于代码中对结果取反,因此取反之后的判断为”h == t || ((s = h.next) != null && s.thread == Thread.currentThread());”,总结起来有两种情况可以通过!hasQueuedPredecessors()这个判断:

  • h==t,h==t的情况为要么当前FIFO队列中没有任何数据,要么只构建出了一个head还没往后面连过任何一个Node,因此head就是tail
  • (s = h.next) != null && s.thread == Thread.currentThread(),当前线程为正在等待的第一个Node中的线程

3. 如果没有线程比当前线程等待更久去执行acquire操作,那么通过CAS操作将state从0变为1的线程tryAcquire成功

4. 没有tryAcquire成功的线程,按照tryAcquire的先后顺序,构建为一个FIFO队列,即第一个tryAcquire失败的排在head的后一位,第二个tryAcquire失败的排在head的后二位

5. 当tryAcquire成功的线程release完毕,第一个tryAcquire失败的线程第一个尝试tryAcquire,这就是先到先得,典型的公平锁

非公平模式ReentrantLock实现原理

看完了公平模式ReentrantLock,接着我们看一下非公平模式ReentrantLock是如何实现的。NonfairSync类,同样是继承自Sync类,实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
 
    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
 
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

结合nonfairTryAcquire方法一起讲解,nonfairTryAcquire方法的实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

看到差别就在于非公平锁lock()的时候会先尝试通过CAS看看能不能把state从0变为1(即获取锁),如果可以的话,直接获取锁而不需要排队。举个实际例子就很好理解了:

  1. 线程1、线程2、线程3竞争锁,线程1竞争成功获取锁,线程2、线程3依次排队
  2. 线程1执行完毕,释放锁,state变为0,唤醒了第一个排队的线程2
  3. 此时线程4来尝试获取锁了,由于线程2被唤醒了,因此线程2与线程4竞争锁
  4. 线程4成功将state从0变为1,线程2竞争锁失败,继续park

看到整个过程中,后来的线程4反而比先来的线程2先获取锁,相当于是一种非公平的模式,

那为什么非公平锁效率会比公平锁效率高?上面第(3)步如果线程2和线程4不竞争锁就是答案。为什么这么说,后面的解释很重要,希望大家可以理解:

线程1是先将state设为0,再去唤醒线程2,这两个过程之间是有时间差的。

那么如果线程1将state设置为0的时候,线程4就通过CAS算法获取到了锁,且在线程1唤醒线程2之前就已经使用完毕锁,那么相当于线程2获取锁的时间并没有推迟,在线程1将state设置为0到线程1唤醒线程2的这段时间里,反而有线程4获取了锁执行了任务,这就增加了系统的吞吐量,相当于单位时间处理了更多的任务。

从这段解释我们也应该能看出来了,非公平锁比较适合加锁时间比较短的任务。这是因为加锁时间长,相当于线程2将state设为0并去唤醒线程2的这段时间,线程4无法完成释放锁,那么线程2被唤醒由于没法获取到锁,又被阻塞了,这种唤醒-阻塞的操作会引起线程的上下文切换,继而影响系统的性能。

Semaphore实现原理

Semaphore即信号量,用于控制代码块的并发数,将Semaphore的permits设置为1相当于就是synchronized或者ReentrantLock,Semaphore具体用法可见Java多线程19:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger。信号量允许多条线程获取锁,显然它的锁是一种共享锁,信号量也有公平模式与非公平模式,相信看懂了上面ReentrantLock的公平模式与非公平模式的朋友应该对Semaphore的公平模式与非公平模式理解起来会更快,这里就放在一起写了。

首先还是看一下Semaphore的基础设施,它和ReentrantLock一样,也有一个Sync:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
 
    Sync(int permits) {
        setState(permits);
    }
 
    final int getPermits() {
        return getState();
    }
 
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
 
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next))
                return true;
        }
    }
 
    final void reducePermits(int reductions) {
        for (;;) {
            int current = getState();
            int next = current - reductions;
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            if (compareAndSetState(current, next))
                return;
        }
    }
 
    final int drainPermits() {
        for (;;) {
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

和ReentrantLock的Sync差不多,Semaphore的Sync定义了以下的一些主要内容:

  1. getPermits方法获取当前的许可剩余量还剩多少,即还有多少线程可以同时获得信号量
  2. 定义了非公平信号量获取共享锁的逻辑nonfairTryAcquireShared
  3. 定义了公平模式释放信号量的逻辑tryReleaseShared,相当于释放一次信号量,state就向上+1(信号量每次的获取与释放都是以1为单位的)

再看下公平信号量的实现,同样的FairSync,继承自Sync,代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;
 
    FairSync(int permits) {
        super(permits);
    }
 
    protected int tryAcquireShared(int acquires) {
        for (;;) {
            if (hasQueuedPredecessors())
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

首先第10行的hasQueuedPredecessors方法,前面已经说过了,如果已经有了FIFO队列或者当前线程不是FIFO队列中在等待的第一条线程,返回-1,表示无法获取共享锁成功。

接着获取available,available就是state,用volatile修饰,所以线程中可以看到最新的state,信号量的acquires是1,每次获取信号量都对state-1,两种情况直接返回:

  1. remaining减完<0
  2. 通过cas设置成功

之后就是和之前说过的共享锁的逻辑了,如果返回的是一个<0的数字,那么构建FIFO队列,线程阻塞,直到前面的执行完才能唤醒后面的。

接着看一下非公平信号量的实现,NonfairSync继承Sync:

1
2
3
4
5
6
7
8
9
10
11
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
 
    NonfairSync(int permits) {
        super(permits);
    }
 
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

nonfairTryAcquireShared在父类已经实现了,再贴一下代码:

1
2
3
4
5
6
7
8
9
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

看到这里和公平Semaphore只有一点差别:不会前置进行一次hasQueuedPredecessors()判断。即当前有没有构建为一个FIFO队列,队列里面第一个等待的线程是不是自身都无所谓,对于非公平Semaphore都一样,反正线程调用Semaphore的acquire方法就将当前state-1,如果得到的remaining设置成功或者CAS操作成功就返回,这种操作没有遵循先到先得的原则,即非公平信号量。

至于非公平信号量对比公平信号量的优点,和ReentrantLock的非公平锁对比ReentrantLock的公平锁一样,就不说了。

CountDownLatch实现原理

CountDownLatch即计数器自减的一种闭锁,某线程阻塞,对一个计数器自减到0,此线程被唤醒,CountDownLatch具体用法可见Java多线程19:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger。

CountDownLatch是一种共享锁,通过await()方法与countDown()两个方法实现自身的功能,首先看一下await()方法的实现:

1
2
3
public void await() throws InterruptedException {
      sync.acquireSharedInterruptibly(1);
 }

acquireSharedInterruptibly最终又回到tryAcquireShared方法上,直接贴整个Sync的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;
 
    Sync(int count) {
        setState(count);
    }
 
    int getCount() {
        return getState();
    }
 
    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }
 
    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

其实看到tryAcquireShared方法,理解AbstractQueuedSynchronizer共享锁原理的,不用看countDown方法应该都能猜countDown方法是如何实现的。我这里总结一下:

  1. 传入一个count,state就等于count,await的时候判断是不是0,是0返回1表示成功,不是0返回-1表示失败,构建FIFO队列,head头只连接一个Node,Node中的线程就是调用CountDownLatch的await()方法的线程
  2. 每次countDown的时候对state-1,直到state减到0的时候才算tryReleaseShared成功,tryReleaseShared成功,唤醒被挂起的线程

为了验证(2),看一下上面Sync的tryReleaseShared方法就可以了,确实是这么实现的。

再理解独占锁与共享锁的区别

本文详细分析了ReentrantLock、Semaphore、CountDownLatch的实现原理,第一个是基于独占锁的实现,后两个是基于共享锁的实现,从这三个类我们可以再总结一下独占锁与共享锁的区别,主要在两点上:

  1. 独占锁同时只有一条线程可以acquire成功,独占锁同时可能有多条线程可以acquire成功,Semaphore是典型例子;
  2. 独占锁每次只能唤醒一个Node,共享锁每次唤醒的时候可以将状态向后传播,即可能唤醒多个Node,CountDownLatch是典型例子。

带着这两个结论再看ReentrantLock、Semaphore、CountDownLatch,你一定会对独占锁与共享锁理解更深。

查看评论

java基于AbstractQueuedSynchronizer实现资源共享锁,限制并发线程数目

学过java的童鞋都知道,如果你要保证一个资源一个方法只允许互斥访问,那你可以使用synchronized关键字最简单了,它能保证,一段代码,一个方法或一个对象只能同时被一个线程使用,如果线程1在使用...
  • 10km
  • 10km
  • 2015-09-22 15:54:47
  • 865

java并发:AbstractQueuedSynchronizer的介绍和原理分析

本文属于java并发系列,原文地址:http://ifeve.com/introduce-abstractqueuedsynchronizer/ 并发编程网有原文, 粗看一遍,很多地方不理解。今天在看...
  • bohu83
  • bohu83
  • 2016-04-11 18:23:44
  • 1916

AbstractQueuedSynchronizer 之 互斥锁

AbstractQueuedSynchronizer 之 互斥锁acquire源码如下:/** * Acquires in exclusive mode, ignoring interrupts. ...
  • Alone_Rojer
  • Alone_Rojer
  • 2017-08-23 15:36:28
  • 84

再谈AbstractQueuedSynchronizer3:基于AbstractQueuedSynchronizer的并发类实现

基于AbstractQueuedSynchronizer的并发类是如何实现的。
  • dandanfeng86
  • dandanfeng86
  • 2017-08-22 22:47:57
  • 56

java怎么实现多线程同步

http://blog.csdn.net/pdw2009/article/details/52373947 Java提供了很多同步操作,比如synchronized关键字、wait/noti...
  • u011220611
  • u011220611
  • 2016-12-26 10:40:31
  • 144

java并发编程--AbstractQueuedSynchronizer条件锁分析(四)

前一篇J.U.C的锁的获取与释放的过程,这个过程主要通过在A.Q.S中维持一个等待队列来实现,其中我们也提到了,在A.Q.S中除了一个等待队列之外,还有多个Condition队列(Condition队...
  • qq_22929803
  • qq_22929803
  • 2015-08-13 11:31:09
  • 432

AbstractQueuedSynchronizer源码剖析(六)- 深刻解析与模拟线程竞争资源

一、细说AQS       在深入分析AQS之前,我想先从AQS的功能上说明下AQS,站在使用者的角度,AQS的功能可以分为两类:独占锁和共享锁。它的所有子类中,要么实现并使用了它独占锁的API...
  • pfnie
  • pfnie
  • 2016-11-19 15:24:29
  • 6497

Java并发之AbstractQueuedSynchronizer分析

在并发程序中一般使用锁对共享变量进行互斥访问。在java程序中一般用synchronized关键字来实现线程对共享变量的互斥访问。而从JDK1.5以后java并发大师 Doug Lea 开发了Abst...
  • c275046758
  • c275046758
  • 2016-02-03 21:05:31
  • 1195

java类库concurrent中最核心类型AbstractQueuedSynchronizer的学习

首先,concurrent类库在java中应该算是非常重要的类库了,在构建一些同步代码,容器,并发什么的都可以在这个类库中找到现成的可以用... 而在这个类库中最最核心的一个类型就是AbstractQ...
  • fjs_cloud
  • fjs_cloud
  • 2014-03-30 21:00:52
  • 1015

java并发编程——九 AbstractQueuedSynchronizer AQS详解

AbstractQueuedSynchronizer概述AQS用来实现锁或其他同步组件的基础框架(注意区别synchronized是在字节码上加指令方式,通过底层机器语言保证同步),AQS使用int类...
  • lemon89
  • lemon89
  • 2016-03-27 21:38:36
  • 4055
    个人资料
    等级:
    访问量: 43万+
    积分: 8882
    排名: 2707
    最新评论