Java之JUC系列(04)--获取公平锁

时间:2021-12-29 16:58:21

一、基本概述

(1)AQS–指AbstractQueuedSynchronizer类
AQS是Java中管理锁的抽象类,锁的许多公共方法都是在这个类中实现。AQS是独占锁(如ReentrantLock)和共享锁(如Semaphore)的公共父类。
(2)AQS锁的类别–分为独占锁和共享锁两种。
1>独占锁:锁在一个时间点只能被一个线程锁占有,根据锁的获取机制,又划分为公平锁和非公平锁。公平锁,是按照通过CLH等待线程队列,依据先后顺序的获取规则,获取该获取锁。而非公平锁,则当线程要获取锁时,它会无视线程等待队列CLH而直接获取锁。独占锁的例子有ReentrantLock,ReentrantReadWritelock
.WriteLock也是独占锁。
2>共享锁:能被多个线程同时拥有,能被共享的锁,JUC 包中的
ReentrantReadWriteLock.ReadLock,CyclicBarrier,CountDownLatch和Semaphore都是共享锁。
(3)CLH队列–Craig, Landin, and Hagersten lock queue
是指AQS中等待锁的线程队列。在进行多线程访问时,竞争的资源在同一个时间点只能被一个线程访问,而其他线程则需要等待,而CLH就是管理这些等待的线程。同时CLH是一个非阻塞的FIFO队列,即添加一个线程时,在并发条件下,并不会阻塞,而是通过自旋锁和CAS保证节点插入和移除的原子性。
(4)CAS函数–Compare and Swap
CAS函数,是比较并交换函数,它是原子操作函数;即通过CAS操作的数据都是以原子方式进行的,例如:例如,compareAndSetHead(), compareAndSetTail(), compareAndSetNext()等函数。它们共同的特点是,这些函数所执行的动作是以原子的方式进行的。

二、公平锁的获取

首先我们得知道,获取锁都是通过Lock()方法,在获取公平锁的过程中,则围绕这个进行。
(1)lock()
lock()在ReentrantLock.java的FairSync类中实现,源码如下:


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

源码分析,当前线程是通过acquire(1)获取锁的,其中”1”表示设置锁的状态。对于“独占锁”来说,锁处于可获取状态时,它的状态值是0;锁被线程初次获取到了,它的状态值就变成了1。
由于ReentrantLock(公平锁/非公平锁)是可重入锁,所以“独占锁”可以被单个线程多此获取,每获取1次就将锁的状态+1。也就是说,初次获取锁时,通过acquire(1)将锁的状态值设为1;再次获取锁时,将锁的状态值设为2;依次类推…这就是为什么获取锁时,传入的参数是1的原因了。
(2)acquire()
acquire()在AQS中实现的,它的源码如下:


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

源码分析:

1> “当前线程”首先通过tryAcquire()尝试获取锁。获取成功的话,直接返回;尝试失败的话,进入到等待队列排序等待(前面还有可能有需要线程在等待该锁)。
2> “当前线程”尝试失败的情况下,先通过addWaiter(Node.EXCLUSIVE)来将“当前线程”加入到”CLH队列(非阻塞的FIFO队列)”末尾。CLH队列就是线程等待队列。
3> 再执行完addWaiter(Node.EXCLUSIVE)之后,会调用acquireQueued()来获取锁。由于此时ReentrantLock是公平锁,它会根据公平性原则来获取锁。
4> “当前线程”在执行acquireQueued()时,会进入到CLH队列中休眠等待,直到获取锁了才返回!如果“当前线程”在休眠等待过程中被中断过,acquireQueued会返回true,此时”当前线程”会调用selfInterrupt()来自己给自己产生一个中断。

文章只是作为自己的学习笔记,借鉴了网上的许多案例,如果觉得阔以的话,希望多交流,在此谢过…