Java多线程系列--“JUC锁”05之 非公平锁

获取非公平锁(基于JDK1.7.0_40)

非公平锁和公平锁在获取锁的方法上,流程是一样的;它们的区别主要表现在“尝试获取锁的机制不同”。简单点说,“公平锁”在每次尝试获取锁时,都是采用公平策略(根据等待队列依次排序等待);而“非公平锁”在每次尝试获取锁时,都是采用的非公平策略(无视等待队列,直接尝试获取锁,如果锁是空闲的,即可获取状态,则获取锁)。
在前面的“Java多线程系列--“JUC锁”03之 公平锁(一)”中,已经详细介绍了获取公平锁的流程和机制;下面,通过代码分析以下获取非公平锁的流程。

1. lock()

lock()在ReentrantLock.java的NonfairSync类中实现,它的源码如下:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

说明
lock()会先通过compareAndSet(0, 1)来判断“锁”是不是空闲状态。是的话,“当前线程”直接获取“锁”;否则的话,调用acquire(1)获取锁。
(01) compareAndSetState()是CAS函数,它的作用是比较并设置当前锁的状态。若锁的状态值为0,则设置锁的状态值为1。
(02) setExclusiveOwnerThread(Thread.currentThread())的作用是,设置“当前线程”为“锁”的持有者。

“公平锁”和“非公平锁”关于lock()的对比

公平锁   -- 公平锁的lock()函数,会直接调用acquire(1)。
非公平锁 -- 非公平锁会先判断当前锁的状态是不是空闲,是的话,就不排队,而是直接获取锁。

2. acquire()

acquire()在AQS中实现的,它的源码如下:

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

(01) “当前线程”首先通过tryAcquire()尝试获取锁。获取成功的话,直接返回;尝试失败的话,进入到等待队列依次排序,然后获取锁。
(02) “当前线程”尝试失败的情况下,会先通过addWaiter(Node.EXCLUSIVE)来将“当前线程”加入到"CLH队列(非阻塞的FIFO队列)"末尾。
(03) 然后,调用acquireQueued()获取锁。在acquireQueued()中,当前线程会等待它在“CLH队列”中前面的所有线程执行并释放锁之后,才能获取锁并返回。如果“当前线程”在休眠等待过程中被中断过,则调用selfInterrupt()来自己产生一个中断。

“公平锁”和“非公平锁”关于acquire()的对比

公平锁和非公平锁,只有tryAcquire()函数的实现不同;即它们尝试获取锁的机制不同。这就是我们所说的“它们获取锁策略的不同所在之处”!
在“Java多线程系列--“JUC锁”03之 公平锁(一)”中,已经详细介绍了acquire()涉及到的各个函数。这里仅对它们有差异的函数tryAcquire()进行说明。

非公平锁的tryAcquire()在ReentrantLock.java的NonfairSync类中实现,源码如下:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

nonfairTryAcquire()在ReentrantLock.java的Sync类中实现,源码如下:

final boolean nonfairTryAcquire(int acquires) {
    // 获取“当前线程”
    final Thread current = Thread.currentThread();
    // 获取“锁”的状态
    int c = getState();
    // c=0意味着“锁没有被任何线程锁拥有”
    if (c == 0) {
        // 若“锁没有被任何线程锁拥有”,则通过CAS函数设置“锁”的状态为acquires。
        // 同时,设置“当前线程”为锁的持有者。
        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;
}

说明
根据代码,我们可以分析出,tryAcquire()的作用就是尝试去获取锁。
(01) 如果“锁”没有被任何线程拥有,则通过CAS函数设置“锁”的状态为acquires,同时,设置“当前线程”为锁的持有者,然后返回true。
(02) 如果“锁”的持有者已经是当前线程,则将更新锁的状态即可。
(03) 如果不术语上面的两种情况,则认为尝试失败。

“公平锁”和“非公平锁”关于tryAcquire()的对比

公平锁和非公平锁,它们尝试获取锁的方式不同。
公平锁在尝试获取锁时,即使“锁”没有被任何线程锁持有,它也会判断自己是不是CLH等待队列的表头;是的话,才获取锁。
而非公平锁在尝试获取锁时,如果“锁”没有被任何线程持有,则不管它在CLH队列的何处,它都直接获取锁。

释放非公平锁(基于JDK1.7.0_40)

非公平锁和公平锁在释放锁的方法和策略上是一样的。

总结
公平锁和非公平锁的区别,是在获取锁的机制上的区别。表现在,在尝试获取锁时 —— 公平锁,只有在当前线程是CLH等待队列的表头时,才获取锁;而非公平锁,只要当前锁处于空闲状态,则直接获取锁,而不管CLH等待队列中的顺序。
只有当非公平锁尝试获取锁失败的时候,它才会像公平锁一样,进入CLH等待队列排序等待。

参考:

http://www.cnblogs.com/skywang12345/p/3496651.html

时间: 08-01

Java多线程系列--“JUC锁”05之 非公平锁的相关文章

Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3534050.html Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可

Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取.ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在

Java多线程系列--“JUC锁”03之 公平锁(一)

基本概念 本章,我们会讲解"线程获取公平锁"的原理:在讲解之前,需要了解几个基本概念.后面的内容,都是基于这些概念的:这些概念可能比较枯燥,但从这些概念中,能窥见"java锁"的一些架构,这对我们了解锁是有帮助的.1. AQS -- 指AbstractQueuedSynchronizer类.    AQS是java中管理"锁"的抽象类,锁的许多公共方法都是在这个类中实现.AQS是独占锁(例如,ReentrantLock)和共享锁(例如,Semap

Java多线程系列--“JUC锁”04之 公平锁(二)

释放公平锁(基于JDK1.7.0_40) 1. unlock() unlock()在ReentrantLock.java中实现的,源码如下: public void unlock() { sync.release(1); } 说明:unlock()是解锁函数,它是通过AQS的release()函数来实现的.在这里,"1"的含义和"获取锁的函数acquire(1)的含义"一样,它是设置"释放锁的状态"的参数.由于"公平锁"是可重

Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例

概要 本章介绍JUC包中的CyclicBarrier锁.内容包括:CyclicBarrier简介CyclicBarrier数据结构CyclicBarrier源码分析(基于JDK1.7.0_40)CyclicBarrier示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3533995.html CyclicBarrier简介 CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier p

Java多线程系列--“JUC锁”07之 LockSupport

概述 本章介绍JUC(java.util.concurrent)包中的LockSupport.内容包括:LockSupport介绍LockSupport函数列表LockSupport参考代码(基于JDK1.7.0_40)LockSupport示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3505784.html LockSupport介绍 LockSupport是用来创建锁和其他同步类的基本线程阻塞原语. LockSupport中的park()

Java多线程系列--“JUC线程池”05之 线程池原理(四)

拒绝策略介绍 线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施.当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭.第二,任务数量超过线程池的最大限制. 线程池共包括4种拒绝策略,它们分别是:AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy和DiscardPolicy. AbortPolicy -- 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常. Calle

Java多线程系列--“JUC线程池”03之 线程池原理(二)

线程池示例 在分析线程池之前,先看一个简单的线程池示例. import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; public class ThreadPoolDemo1 { public static void main(String[] args) { // 创建一个可重用固定线程数的线程池 ExecutorService pool = Executors.newFixedThre

Java多线程系列--“JUC线程池”02之 线程池原理(一)

ThreadPoolExecutor简介 ThreadPoolExecutor是线程池类.对于线程池,可以通俗的将它理解为"存放一定数量线程的一个线程集合.线程池允许同时运行的线程数量就是线程池的容量:当添加到线程池中的线程超过它的容量时,会有一部分线程阻塞等待.线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理." ThreadPoolExecutor数据结构 ThreadPoolExecutor的数据结构如下图所示: 各个数据在ThreadPoolExecutor