公平机制:对于公平机制和非公平机制进行介绍,包含对比
实现:Sync源码解析额,公平和非公平模式的加锁、解锁过程及源码分析
公平锁和非公平锁的加锁流程图
ReentrantLock提供的一些其他方法
Condition:这里只是一提,不会有什么有意义的内容
ReentrantLock全部源码理解:个人阅读ReentrantLock源码的时候做的注释,需要结合AQS一块理解
Tips:同步等待队列:即普遍的资料中提到的同步队列,由AQS维护。在代码的英文注释中写道wait queue,因此我在这里翻译成同步等待队列
条件等待队列:即普遍的资料中提到的等待队列,由AQS.Condition维护,在代码的英文注释中也是写道wait queue,因此我在这里翻译成条件等待队列
本篇文章会大量提到AQS这个类,并且大量的方法都在AQS中实现,本文对会对使用到的方法进行解释,但是对于AQS内部的属性没有过多解释,后续篇章写AQS会专门写,建议可以了解一下,有助于理解
建议阅读前先了解AQS中的Node类,有助于阅读,本文不会说明
ReentrantLock简介在多线程编程中,同步和互斥是一个非常重要的问题。
在java中可以通过使用synchronized来实现共享资源的独占,
除此之外还可以使用Lock提供的方法来实现对共享资源的独占。
而且Lock对比synchronized具有更高的灵活性。
ReentrantLock是Lock接口的一种实现,它提供了公平和非公平两种锁的公平机制供开发者选择,
并且实现了锁的可重入性(指的是对同一临界资源重复加锁,注意:加锁多少次就一定要解锁多少次)。
ReentrantLock提供了公平锁和非公平锁两个版本供开发者选择:
公平锁:所有请求获取锁的线程按照先后顺序排队获取锁,下一个获取锁的线程一定是等候获取锁时间最长的线程,所得获取满足FIFO特点。
非公平锁:请求获取锁的线程不一定需要排队,只要锁没有被获取,不论是否有线程在等待获取所,他就可以进行加锁操作。eg:所有在队列中等待锁的线程都没有被操作系统调度到并且锁没有被获取,此时正在指定的线程需要获取锁就可以直接尝试获取锁
对比 公平锁 非公平锁优点 所有的线程都可以获取到资源不会饿死在队列当中 可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量
缺点 吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大 可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死
实现 抽象类Sync
公平与非公平的实现主要靠锁的同步器来实现,他们都是内部抽象类Sync的子类(姑且称之为抽象同步器)。
\(\color{#FF3030}{Sync及父类AQS提供了整个加锁和解锁的过程及排队等待的过程,}\)并暴露出抽象方法lock()供实现不同的锁公平机制。
ReentrantLock中Sync的子类NonfairSync提供非公平锁同步机制,FairSync提供公平的锁同步机制。
代码的解释请看注释
/** * Base of synchronization control for this lock. Subclassed * into fair and nonfair versions below. Uses AQS state to * represent the number of holds on the lock. */ //提供了这个锁的同步器的基础方法,子类NonfairSync和FairSync提供了公平和非公平两种同步器的实现 //使用AQS的state来标识锁的状态 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. */ //默认提供了非公平机制的加锁过程 //acquires 申请加锁的次数,一般情况下是一次,但是有多次的情况,在Condition中会看到 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //获取锁的状态,getState在AQS中实现 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; } //释放锁的过程 //releases释放锁的次数,一般情况下是一次,但是有多次的情况,在Condition中会看到 protected final boolean tryRelease(int releases) { //getState在AQS中实现 int c = getState() - releases; //独占锁释放锁的时候谁获取的锁谁用完释放,期间不许其他线程使用 /如果锁的拥有者不是当前线程代码结构则出了问题 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); //由于可重入性,只有在释放releases次后锁状态为0才完全释放锁,锁才不被占有 boolean free = false; //如果释放锁后锁状态为0,则表示当前线程不再持有这个锁 //则将持有锁的线程exclusiveOwnerThread置null if (c == 0) { free = true; setExclusiveOwnerThread(null); } //设置锁状态,,在AQS中实现 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(); } //条件等待队列 //具体实现在AQS中实现 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 the instance from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } } 公平锁