摘要:概述前面已经讲解了关于的非公平锁模式,关于非公平锁,内部其实告诉我们谁先争抢到锁谁就先获得资源,下面就来分析一下公平锁内部是如何实现公平的如果没有看过非公平锁的先去了解下非公平锁,因为这篇文章前面不会讲太多内部结构,直接会对源码进行分析前文
概述
前面已经讲解了关于AQS的非公平锁模式,关于NonfairSync非公平锁,内部其实告诉我们谁先争抢到锁谁就先获得资源,下面就来分析一下公平锁FairSync内部是如何实现公平的?如果没有看过非公平锁的先去了解下非公平锁,因为这篇文章前面不会讲太多内部结构,直接会对源码进行分析
前文连接地址:图解AQS原理之ReentrantLock详解-非公平锁
本文分析的JDK版本是1.8源码分析温馨提示:读本文内容建议结合之前写的非公平,前篇设计了很多基础性内容
在源码分析之前,我们先来看一下ReentrantLock如何切换获取锁的模式呢?其实是在构造器中传递指定的类型变量来控制使用锁的方式,如下所示:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
当fair参数指定为true时,代表的是公平锁,如果指定为false则使用的非公平,无参的构造函数默认使用的是非公平模式,如下所示:
public ReentrantLock() { sync = new NonfairSync(); }
接下来我们以一个例子来进行后面的说明:
public class ReentrantLockDemo { public static void main(String[] args) throws Exception { AddDemo runnalbeDemo = new AddDemo(); Thread thread = new Thread(runnalbeDemo::add); thread.start(); Thread.sleep(500); Thread thread1 = new Thread(runnalbeDemo::add); thread1.start(); System.out.println(runnalbeDemo.getCount()); } private static class AddDemo { private final AtomicInteger count = new AtomicInteger(); private final ReentrantLock reentrantLock = new ReentrantLock(true); private final Condition condition = reentrantLock.newCondition(); private void add() { try { reentrantLock.lockInterruptibly(); count.getAndIncrement(); } catch (Exception ex) { System.out.println("线程被中断了"); } finally { // reentrantLock.unlock(); } } int getCount() { return count.get(); } } }
我们通过源码可以看到这里我们启动了两个线程,两个线程分别进行同步锁操作,这里我并没有释放掉锁,因为方便分析队列的情况,当然你也可以在内部写一个死循环,不释放锁就可以了,我这里简单的不释放锁,使用的是可中断的获取锁操作方法lockInterruptibly,这里内部的原理我们上一篇文章中已经讲解过了,这里并不过多的去分析内部原理,这个ReentrantLock的lockInterruptibly调用内部类AQS的acquireInterruptibly,但是其实是FairSync内部类继承了内部类Sync,而内部类Sync有继承了AbstractQueuedSynchronizer简称AQS,acquireInterruptibly源码信息如下所示:
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
这里我们通过上一篇文章得知tryAcquire是需要子类去实现的方法,我们在例子中指定了使用的是公平锁,所以tryAcquire方法的实现是在ReentrentLock的FairSync类中,我们来具体看一下这个方法,重点也在这个方法中其他的其实都是一样的,因为用的方法都会一样的非公平和公平锁的调用,唯独不一样的就是子类实现的方法是不相同的,接下来我们就来看一下公平锁的tryAcquire是如何实现的?
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; //state递增 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); //设置state状态 return true; //获得锁成功 } return false; //获得锁失败 }
对比非公平锁的NonfairSync类的tryAcquire方法,其实就是在锁可用的情况下增加了一个判断条件,这个判断方法就是hasQueuedPredecessors,从方法的名称来看说的是有等待的线程队列,换句话说已经有人在排队了,新来的线程你就不能加塞,而非公平模式的谁先争抢到锁就是谁的,管你先来不先来,接下来我们具体看一下这个
hasQueuedPredecessors方法源码:
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // 获得尾节点 Node h = head; // 获得头节点 Node s; return h != t && //头节点和尾节点相同代表队列为空 ((s = h.next) == null || s.thread != Thread.currentThread()); //头节点的next节点为空代表头节点,以及s.thread不是当前线程不是自己的话代表队列中存在元素 }
通过上面的源码信息,可以得出其实内部主要就是判断有没有排队等待的节点,队列是否为空,如果为空的话则可以争抢锁,如果队列不为空,伙计你必须老老实实给我排队去,除非占有锁的线程和请求锁的线程是一样的,否则还是老老实实排队去,这就是公平模式的锁操作,还有一个lock方法,公平模式的lock方法,没有直接上来先获取锁,而是先尝试获得锁直接调用AQS的aquire方法进行尝试获取锁,下面是FairSync源码:
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); //这里直接调用了aquire并没有尝试修改state状态 } /** * 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; } }结束语
本内容主要是结合上篇内容的一个续篇,可以结合上篇然后再看下篇会比较清晰些。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/74749.html
摘要:内部提供了两种的实现,一种公平模式,一种是非公平模式,如果没有特别指定在构造器中,默认是非公平的模式,我们可以看一下无参的构造函数。 概述 并发编程中,ReentrantLock的使用是比较多的,包括之前讲的LinkedBlockingQueue和ArrayBlockQueue的内部都是使用的ReentrantLock,谈到它又不能的不说AQS,AQS的全称是AbstractQueue...
摘要:所以大家看下面的图,就是线程跑过来加锁的一个过程。好线程现在就重新尝试加锁,这时还是用操作将从变为,此时就会成功,成功之后代表加锁成功,就会将设置为。此外,还要把加锁线程设置为线程自己,同时线程自己就从等待队列中出队了。 ReentrantLock和AQS的关系 ReentrantLock使用 showImg(https://segmentfault.com/img/bVbkZr4?...
摘要:的主要功能和关键字一致,均是用于多线程的同步。而仅支持通过查询当前线程是否持有锁。由于和使用的是同一把可重入锁,所以线程可以进入方法,并再次获得锁,而不会被阻塞住。公平与非公平公平与非公平指的是线程获取锁的方式。 1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似。所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻...
摘要:开始获取锁终于轮到出场了,的调用过程和完全一样,同样拿不到锁,然后加入到等待队列队尾然后,在阻塞前需要把前驱结点的状态置为,以确保将来可以被唤醒至此,的执行也暂告一段落了安心得在等待队列中睡觉。 showImg(https://segmentfault.com/img/remote/1460000016012467); 本文首发于一世流云的专栏:https://segmentfault...
摘要:公平策略在多个线程争用锁的情况下,公平策略倾向于将访问权授予等待时间最长的线程。使用方式的典型调用方式如下二类原理的源码非常简单,它通过内部类实现了框架,接口的实现仅仅是对的的简单封装,参见原理多线程进阶七锁框架独占功能剖析 showImg(https://segmentfault.com/img/remote/1460000016012582); 本文首发于一世流云的专栏:https...
阅读 3894·2021-09-09 09:33
阅读 1734·2021-09-06 15:14
阅读 1896·2019-08-30 15:44
阅读 3044·2019-08-29 18:36
阅读 3732·2019-08-29 16:22
阅读 2073·2019-08-29 16:21
阅读 2492·2019-08-29 15:42
阅读 1629·2019-08-29 11:00