Lock体系-ReentrantLock、ReentrantReadWriteLock
ReentrantLock源码分析
1、类的继承关系
ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。
public class ReentrantLock implements Lock, java.io.Serializable

-
Sync类

abstract static class Sync extends AbstractQueuedSynchronizer { // 序列号 private static final long serialVersionUID = -5179523762034025860L; // 获取锁 abstract void lock(); // 非公平方式获取 final boolean nonfairTryAcquire(int acquires) { // 当前线程 final Thread current = Thread.currentThread(); // 获取状态 int c = getState(); if (c == 0) { // 表示没有线程正在竞争该锁 if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用 // 设置当前线程独占 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; } // 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它 protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程不为独占线程 throw new IllegalMonitorStateException(); // 抛出异常 // 释放标识 boolean free = false; if (c == 0) { free = true; // 已经释放,清空独占 setExclusiveOwnerThread(null); } // 设置标识 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(); } // 新生一个条件 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 } }
-
NonfairSync类
NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法。从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。
源码如下:
// 非公平锁 static final class NonfairSync extends Sync { // 版本号 private static final long serialVersionUID = 7316153563782823691L; // 获得锁 final void lock() { if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用 // 把当前线程设置独占了锁 setExclusiveOwnerThread(Thread.currentThread()); else // 锁已经被占用,或者set失败 // 以独占模式获取对象,忽略中断 acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
-
FairSyn类
FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法.跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。
/ 公平锁 static final class FairSync extends Sync { // 版本序列化 private static final long serialVersionUID = -3000897897090466540L; final void lock() { // 以独占模式获取对象,忽略中断 acquire(1); } /** * 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) { // 状态为0 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功 // 设置当前线程独占 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据 // 下一个状态 int nextc = c + acquires; if (nextc < 0) // 超过了int的表示范围 throw new Error("Maximum lock count exceeded"); // 设置状态 setState(nextc); return true; } return false; } }

2、类的属性及构造函数
ReentrantLock类的sync非常重要,对ReentrantLock类的操作大部分都直接转化为对Sync和AbstractQueuedSynchronizer类的操作。
public class ReentrantLock implements Lock, java.io.Serializable { // 序列号 private static final long serialVersionUID = 7373984872572414699L; // 同步队列 private final Sync sync; }
默认是采用的非公平策略获取锁
public ReentrantLock() { // 默认非公平策略 sync = new NonfairSync(); }
可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
3、核心函数分析
4、ReentrantLock中的公平锁和非公平锁的主要区别为
公平锁中, 线程严格按照先进先出(FIFO)的顺序, 获取锁资源
非公平锁中, 拥有锁的线程在释放锁资源的时候, 当前尝试获取锁资源的线程可以和等待队列中的第一个线程竞争锁资源, 这就是ReentrantLcok中非公平锁的含义; 但是已经进入等待队列的线程, 依然是按照先进先出的顺序获取锁资源
非公平锁的弊端:可能导致后面排队等待的线程等不到相应的CPU资源,从而引起线程饥饿。


注意: 以上图片中的Head是一个特殊的节点, 仅用于构造等待队列这个数据结构, 不包含等待的线程信息, 所以下一个可以获取锁资源的节点是Node1节点
另外, 上锁的过程本身也是有时间开销的,如果操作资源的时间比上锁的时间还短建议使用非公平锁可以提高系统的吞吐率;否则就老老实实的用公平锁。
5、ReentrantLock和Synchronized的对比
都是阻塞
ReentrantLock和synchronized都是加锁式同步,当一个线程获取了对象锁后,其它要进入同步块的线程就必须阻塞在同步块外等待。线程的阻塞和唤醒需要操作系统在用户态和内核态之间切换,所以,ReentrantLock和synchronized都是代价比较高的。
实现方式
synchronized是java语言的关键字,它的锁机制是由jvm实现的,是原生语法层面上的互斥,最底层是mutex。而ReentrantLock则是JDK1.5之后,提供的api层面 的锁,需要在代码中显示调用lock、unlock等方法来完成。
所以,从便利性来说,synchronized使用起来更简单一些。但是从灵活度来说,ReentrantLock更灵活,可控性也更强,可实现更细粒度的锁。但是,在使用ReentrantLock时,一定要注意lock和unlock的匹配和顺序,否则就可能造成死锁。常见的方案是把unlock放在异常处理的finally语句块中。
性能
synchronized是非公平锁,并且它无法实现公平锁。要实现公平锁,可以通过ReentrantLock来实现。通过new ReentrantLock(true)可以用来构造一个公平锁。
是否可重入锁
synchronized就是一把可重入锁。synchronized就是利用monitor来实现的。Monitor,直译为监视器,底层实现是Mutex。JVM会给每个对象和class字节码设置一个monitor。当某个线程要进入某个同步块是,就需要获得对应目标的monitor。换句话,当某个线程获得了对应目标的monitor,它就进入了同步块。当该线程执行完同步块,或被挤占而等待时,就会让出monitor
当然了,使用ReentrantLock也可以实现可重入锁
等待可中断
等待可中断是使用ReentrantLock时,可以实现的一个机制。当某个线程等待锁过长时间时,程序可以通过lockInterruptibly方法来使当前线程中断等待,转去执行其它的线程。
线程分组唤醒
有些场景下,我们可能不希望唤醒所有的线程,而是唤醒部分线程。这种方式在synchronized下是无法实现的。但是,ReentrantLock通过提供一个Contition类,可以同时绑定多个对象,以此,来实现线程的分组唤醒。
什么时候选择用 ReentrantLock 代替 synchronized
在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者轮询锁。 ReentrantLock 还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock “性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
读写锁ReentrantReadWriteLock的介绍
在分析WirteLock和ReadLock的互斥性时可以按照WriteLock与WriteLock之间,WriteLock与ReadLock之间以及ReadLock与ReadLock之间进行分析。更多关于读写锁特性介绍大家可以看源码上的介绍(阅读源码时最好的一种学习方式,我也正在学习中,与大家共勉),这里做一个归纳总结:
- 公平性选择:支持非公平性(默认)和公平的锁获取方式,吞吐量还是非公平优于公平;
- 重入性:支持重入,读锁获取后能再次获取,写锁获取之后能够再次获取写锁,同时也能够获取读锁;
- 锁降级:遵循获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁
ReentrantReadWriteLock类的继承关系
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {}
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { // 版本序列号 private static final long serialVersionUID = -6992448646407690164L; // 读锁 private final ReentrantReadWriteLock.ReadLock readerLock; // 写锁 private final ReentrantReadWriteLock.WriteLock writerLock; // 同步队列 final Sync sync; private static final sun.misc.Unsafe UNSAFE; // 线程ID的偏移地址 private static final long TID_OFFSET; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> tk = Thread.class; // 获取线程的tid字段的内存地址 TID_OFFSET = UNSAFE.objectFieldOffset (tk.getDeclaredField("tid")); } catch (Exception e) { throw new Error(e); } } }
Sync类内部存在两个内部类
abstract static class Sync extends AbstractQueuedSynchronizer { // 版本序列号 private static final long serialVersionUID = 6317671515068378041L; // 高16位为读锁,低16位为写锁 static final int SHARED_SHIFT = 16; // 读锁单位 static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 读锁最大数量 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 写锁最大数量 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 本地线程计数器 private transient ThreadLocalHoldCounter readHolds; // 缓存的计数器 private transient HoldCounter cachedHoldCounter; // 第一个读线程 private transient Thread firstReader = null; // 第一个读线程的计数 private transient int firstReaderHoldCount; }
// 计数器 static final class HoldCounter { // 计数 int count = 0; // Use id, not reference, to avoid garbage retention // 获取当前线程的TID属性的值 final long tid = getThreadId(Thread.currentThread()); }
// 本地线程计数器 static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { // 重写初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值 public HoldCounter initialValue() { return new HoldCounter(); } }
ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,ThreadLocal类可以将线程与对象相关联。在没有进行set的情况下,get到的均是initialValue方法里面生成的那个HolderCounter对象。
ReentrantReadWriteLock写锁详解
写锁的实现依然也是采用同步组件的实现聚合了同步器(AQS),并通过重写重写同步器(AQS)中的方法实现同步组件的同步语义。在同一时刻写锁是不能被多个线程所获取,很显然写锁是独占式锁,而实现写锁的同步语义是通过重写AQS中的tryAcquire方法实现的。protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ Thread current = Thread.currentThread(); // 1. 获取写锁当前的同步状态 int c = getState(); // 2. 获取写锁获取的次数 int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) // 3.1 当读锁已被读线程获取或者当前线程不是已经获取写锁的线程的话 // 当前线程获取写锁失败 if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire // 3.2 当前线程获取写锁,支持可重复加锁 setState(c + acquires); return true; } // 3.3 写锁未被任何线程获取,当前线程可获取写锁 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }
这里有一个地方需要重点关注,exclusiveCount(c)方法,该方法源码为:
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
根据exclusiveCount方法的注释为独占式获取的次数即写锁被获取的次数,现在就可以得出来一个结论同步状态的低16位用来表示写锁的获取次数。同步状态的高16位用来表示读锁被获取的次数(sharedCount)

现在我们回过头来看写锁获取方法tryAcquire,其主要逻辑为:当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败;否则,获取成功并支持重入,增加写状态。
写锁的释放
写锁释放通过重写AQS的tryRelease方法,源码为:
protected final boolean tryRelease(int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //1. 同步状态减去写状态 int nextc = getState() - releases; //2. 当前写状态是否为0,为0则释放写锁 boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); //3. 不为0则更新同步状态 setState(nextc); return free; }
源码的实现逻辑请看注释,不难理解与ReentrantLock基本一致,这里需要注意的是,减少写状态int nextc = getState() - releases;只需要用当前同步状态直接减去写状态的原因正是我们刚才所说的写状态是由同步状态的低16位表示的。

读锁的获取
现在来看看读锁,读锁不是独占式锁,即同一时刻该锁可以被多个读线程获取也就是一种共享式锁。按照之前对AQS介绍,实现共享式同步组件的同步语义需要通过重写AQS的tryAcquireShared方法和tryReleaseShared方法。读锁的获取实现方法为:
protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. Otherwise, this thread is eligible for * lock wrt state, so ask if it should block * because of queue policy. If not, try * to grant by CASing state and updating count. * Note that step does not check for reentrant * acquires, which is postponed to full version * to avoid having to check hold count in * the more typical non-reentrant case. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ Thread current = Thread.currentThread(); int c = getState(); //1. 如果写锁已经被获取并且获取写锁的线程不是当前线程的话,当前 // 线程获取读锁失败返回-1 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); if (!readerShouldBlock() && r < MAX_COUNT && //2. 当前线程获取读锁 compareAndSetState(c, c + SHARED_UNIT)) { //3. 下面的代码主要是新增的一些功能,比如getReadHoldCount()方法 //返回当前获取读锁的次数 if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } //4. 处理在第二步中CAS操作失败的自旋已经实现重入性 return fullTryAcquireShared(current); }
(1 << SHARED_SHIFT)即0x00010000)的原因这是我们在上面所说的同步状态的高16位用来表示读锁被获取的次数。如果CAS失败或者已经获取读锁的线程再次获取读锁时,是靠fullTryAcquireShared方法实现的
读锁的释放
读锁释放的实现主要通过方法tryReleaseSharedprotected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); // 前面还是为了实现getReadHoldCount等新功能 if (firstReader == current) { // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1) { readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } --rh.count; } for (;;) { int c = getState(); // 读锁释放 将同步状态减去读状态即可 int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; } }
锁降级
读写锁支持锁降级,遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级
void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); try { // Recheck state because another thread might have // acquired write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { use(data); } finally { rwl.readLock().unlock(); } } }
代码示例:
import java.util.concurrent.locks.ReentrantReadWriteLock; class ReadThread extends Thread { private ReentrantReadWriteLock rrwLock; public ReadThread(String name, ReentrantReadWriteLock rrwLock) { super(name); this.rrwLock = rrwLock; } public void run() { System.out.println(Thread.currentThread().getName() + " trying to lock"); try { rrwLock.readLock().lock(); System.out.println(Thread.currentThread().getName() + " lock successfully"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { rrwLock.readLock().unlock(); System.out.println(Thread.currentThread().getName() + " unlock successfully"); } } } class WriteThread extends Thread { private ReentrantReadWriteLock rrwLock; public WriteThread(String name, ReentrantReadWriteLock rrwLock) { super(name); this.rrwLock = rrwLock; } public void run() { System.out.println(Thread.currentThread().getName() + " trying to lock"); try { rrwLock.writeLock().lock(); System.out.println(Thread.currentThread().getName() + " lock successfully"); } finally { rrwLock.writeLock().unlock(); System.out.println(Thread.currentThread().getName() + " unlock successfully"); } } } public class ReentrantReadWriteLockDemo { public static void main(String[] args) { ReentrantReadWriteLock rrwLock = new ReentrantReadWriteLock(); ReadThread rt1 = new ReadThread("rt1", rrwLock); ReadThread rt2 = new ReadThread("rt2", rrwLock); WriteThread wt1 = new WriteThread("wt1", rrwLock); rt1.start(); rt2.start(); wt1.start(); } }
某一次运行结果:

著作权归https://www.pdai.tech所有。 链接:https://www.pdai.tech/md/java/thread/java-thread-x-lock-ReentrantReadWriteLock.html

浙公网安备 33010602011771号