ReentrantLock和AQS

 

  AQS(AbstractQueuedSynchronizer)是JDK1.5提供的一个用来构建锁和同步工具的框架,子类包括常用的ReentrantLock、CountDownLatch、Semaphore等。

  AQS没有锁之类的概念,它有个state变量,是个int类型 ,state 是同步状态位,具体是否能够获取锁就是通过修改state来实现

  AQS的功能可以分为独占和共享,ReentrantLock实现了独占功能

ReentrantLock锁的架构:

  ReentrantLock的内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。

获取锁的过程:

  线程去竞争一个锁,可能成功也可能失败。成功就直接持有资源,不需要进入队列;失败的话进入队列阻塞,等待唤醒后再尝试竞争锁。

公平锁尝试获取锁:

 1 final void lock() { acquire(1);}
 2 
 3 public final void acquire(int arg) {
 4     if (!tryAcquire(arg) &&
 5         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 6         selfInterrupt();
 7 }
 8 
 9 protected final boolean tryAcquire(int acquires) {
10    final Thread current = Thread.currentThread();
11    int c = getState();
12    if (c == 0) {
13        if (!hasQueuedPredecessors() &&
14            compareAndSetState(0, acquires)) {
15            setExclusiveOwnerThread(current);
16            return true;
17        }
18    }
19    else if (current == getExclusiveOwnerThread()) {
20        int nextc = c + acquires;
21        if (nextc < 0)
22            throw new Error("Maximum lock count exceeded");
23        setState(nextc);
24        return true;
25    }
26    return false;
27 }

  第一个if判断AQS的state是否等于0,表示锁没有人占有。

  接着,hasQueuedPredecessors判断队列是否有排在前面的线程在等待锁,没有的话调用compareAndSetState使用cas的方式修改state,将0改为1。

  最后线程获取锁成功,setExclusiveOwnerThread将线程记录为独占锁的线程。

  第二个if判断当前线程是否为独占锁的线程,因为ReentrantLock是可重入的,线程可以不停地lock来增加state的值,对应地需要unlock来解锁,直到state为零。

  如果最后获取锁失败,下一步需要将线程加入到等待队列。

线程进入等待队列:

  AQS内部有一条双向队列存放等待线程,节点是Node对象。每个Node维护了线程、前后Node的指针和等待状态等参数。

  线程在加入队列之前,需要包装进Node,调用方法是addWaiter。

  每个Node需要标记是独占的还是共享的,由传入的mode决定,ReentrantLock自然是使用独占模式Node.EXCLUSIVE。

  创建好Node后,如果队列不为空,使用cas的方式将Node加入到队列尾。注意,这里只执行了一次修改操作,并且可能因为并发的原因失败。因此修改失败的情况和队列为空的情况,需要进入enq()方法。

阻塞等待线程:

  线程加入队列后,下一步是调用acquireQueued阻塞线程。

非公平锁获取锁:

1 final void lock() {
2     if (compareAndSetState(0, 1))
3         setExclusiveOwnerThread(Thread.currentThread());
4     else
5         acquire(1);
6 }

  在NonfairSync的lock方法里,第一步直接尝试将state修改为1,很明显,这是抢先获取锁的过程。如果修改state失败,则和公平锁一样,调用acquire。

  公平锁会关注队列里排队的情况,老老实实按照FIFO的次序;非公平锁只要有机会就抢占,才不管排队的事。

羊群效应:

  当有多个线程去竞争同一个锁的时候,假设锁被某个线程占用,那么如果有成千上万个线程在等待锁,有一种做法是同时唤醒这成千上万个线程去去竞争锁,这个时候就发生了羊群效应,海量的竞争必然造成资源的剧增和浪费,因此终究只能有一个线程竞争成功,其他线程还是要老老实实的回去等待。

  AQS的FIFO的等待队列给解决在锁竞争方面的羊群效应问题提供了一个思路:保持一个FIFO队列,队列每个节点只关心其前一个节点的状态,线程唤醒也只唤醒队头等待线程。其实这个思路已经被应用到了分布式锁的实践中,见:Zookeeper分布式锁的改进实现方案。







原文地址:https://www.cnblogs.com/mengchunchen/p/9250682.html