Java多线程Day23-JUC锁之共享锁

Posted 攻城狮Chova

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程Day23-JUC锁之共享锁相关的知识,希望对你有一定的参考价值。

基本概念

  • JavaJUCjava.util.concurrent中的锁包括独占锁和共享锁
  • JUC中的共享锁包括:
    • CountDownLatch
    • CyclicBarrier
    • Semaphore
    • ReentrantReadWriteLock
  • ReadWriteLock: 读写锁
    • ReadWriteLock中维护了一对相关的锁,也就是读取锁和写入锁
    • 读取锁用于读取操作
      • 用于只读操作,是共享锁,能够被多个线程同时获取
    • 写入锁用于写入操作
      • 用于写入操作,是独占锁,写入锁一个只能被一个线程锁获取
  • 注意: 不能同时存在读取锁和写入锁
  • ReentrantReadWriteLock:
    • ReentrantReadWriteLock是接口ReadWriteLock的实现类
    • ReentrantReadWriteLock包括子类ReadLockWriteLock

ReadWriteLock函数列表

readLock

/**
 * 返回用于读取操作的锁
 *  
 * @return Lock 读取操作的锁
 */
Lock readLock();

writeLock

/**
 * 返回用于写入操作的锁
 *  
 * @return Lock 写入操作的锁
 */
Lock writeLock();

ReentrantReadWriteLock函数列表

ReentrantReadWriteLock

/**
 * 创建一个新的ReentrantReadWriteLock.使用默认的非公平顺序属性
 */
public ReentrantReadWriteLock();

/**
 * 创建一个新的ReentrantReadWriteLock.指定是否使用公平策略
 *  
 * @param fair 是否使用公平策略.如果为true则使用公平策略,如果为false,则使用非公平策略
 */
public ReentrantReadWriteLock(boolean fair)

getOwner

/**
 * 获取持有当前写入锁的线程,如果没有则返回null
 *  
 * @return Thread 持有当前写入锁的线程
 */
protected Thread getOwner();

getQueuedReaderThreads

/**
 * 获取一个包含可能正在等待获取读取锁的线程的集合Collection
 *  
 * @return Collection<Thread> 包含可能正在等待获取读取锁或者写入锁的线程的集合Collection
 */
protected Collection<Thread> getQueuedReaderThreads();

getQueuedThreads

/**
 * 获取一个包含可能正在等待获取读取锁或者写入锁的线程的集合Collection
 *  
 * @return Collection<Thread> 包含可能正在等待获取读取锁或者写入锁的线程的集合Collection
 */
protected Collection<Thread> getQueuedThreads();

getQueuedWriterThreads

/**
 * 获取一个包含可能正在等待获取写入锁的线程的集合Collection
 *  
 * @return Collection<Thread> 包含可能正在等待获取写入锁的线程的集合Collection
 */
protected Collection<Thread> getQueuedWriterThreads();

getQueuedLength

/**
 * 获取一个可能正在等待获取读取锁或者写入锁的线程的数目
 *  
 * @return int 可能正在等待获取读取锁或者写入锁的线程的数目
 */
public final int getQueueLength();

getReadHoldCount

/**
 * 查询当前线程在当前锁上保持的可重入读取锁的数目
 * 已经生成锁,但是未解锁的可重入读取锁的数量
 *  
 * @return int 当前线程在当前锁上保持的可重入读取锁的数目
 */
public int getReadHoldCount();

getReadLockCount

/**
 * 查询当前锁上保持的读取锁的数量
 *  
 * @return int 当前锁上保持的读取锁的数量
 */
public int getReadLockCount();

getWaitingThreads

/**
 * 获取一个包含可能正在等待与写入锁相关的给定条件的线程的集合Collection
 *  
 * @param condition 与写入锁相关的给定条件
 * @return Collection 等待线程的集合Collection
 */
protected Collection<Thread> getWaitingThreads(Condition condition);

getWaitQueuedLength

/**
 * 获取可能正在等待与写入锁相关的给定条件的线程的数目
 * 因为超时条件和线程中断,这个数目是等待线程的上限
 *  
 * @param condition 与写入锁相关的给定条件
 * @return int 等待线程的数目
 */
public int getWaitQueueLength(Condition condition);

getWriteHoldCount

/**
 * 查询当前线程在当前锁上保持的可重入写入锁的数目
 * 已经生成锁,但是未解锁的可重入写入锁的数量
 *  
 * @return int 当前线程在当前锁上保持的可重入写入锁的数目
 */
public int getWriteHoldCount();

hasQueuedThread

/**
 * 判断当前的线程是否正在等待获取读取锁或者写入锁
 *  
 * @return boolean 当前线程是否正在等待获取读取锁或者写入锁
 */
public final boolean hasQueuedThread(Thread thread);

hasQueuedThreads

/**
 * 判断线程是否正在等待获取读取锁或者写入锁
 *  
 * @return boolean 线程是否正在等待获取读取锁或者写入锁
 */
public final boolean hasQueuedThreads();

hasWaiters

/**
 * 判断是否有正在等待获取与写入锁相关的给定条件的线程
 *  
 * @param condition 与写入锁相关的条件
 * @return boolean 是否有正在等待获取与写入锁相关条件的线程
 */
public boolean hasWaiters(Condition condition);

isFair

/**
 * 判断当前锁是否使用了公平策略
 * 
 * @return boolean 当前锁是否使用了公平策略
 */
public final boolean isFair()

isWriteLocked

/**
 * 判断当前写入锁是否被其余的线程持有
 * 
 * @return boolean 当前写入锁是否被其余的线程持有
 */
public boolean isWriteLocked();

isWriteLockedByCurrentThread

/**
 * 判断当前写入锁是否被当前线程持有
 * 
 * @return boolean 当前写入锁是否被当前线程持有
 */
public boolean isWriteLockedByCurrentThread();

readLock

/**
 * 获取读取锁
 * 
 * @return ReadLock 读取锁
 */
public ReentrantReadWriteLock.ReadLock  readLock();

writeLock

/**
 * 获取写入锁
 * 
 * @return WriteLock 写入锁
 */
public ReentrantReadWriteLock.WriteLock writeLock();

ReentrantReadWriteLock数据结构

在这里插入图片描述

  • ReentrantReadWriteLock实现了ReadWriteLock接口 ,ReadWriteLock是一个读写锁接口,提供了获取读取锁readLock() 和获取写入锁writeLock()
  • ReentrantReadWriteLock中包括sync对象,读取锁readLock和写入锁writeLock. 读取锁readLock和写入锁writeLock都实现了Lock接口,也包含sync对象.通过sync对象,读取锁readLock和写入锁writeLock实现了对同一个对象的访问
  • ReentrantReadWriteLockReentrantLock一样 ,sync对象是Sync类型的,并且Sync是一个继承于AQS的抽象类 .Sync中包括公平锁FairSync和非公平锁NonfairSync, 其中sync对象是FairSyncNonFairSync中的一个,默认是NonfairSync

获取共享锁Lock

  • 获取共享锁ReadLock的过程:
    • 先通过tryAcquireShared() 尝试获取共享锁
    • 成功获取共享锁后,直接返回
    • 尝试失败,则通过doAcquireShared() 循环尝试获取共享锁,如果有需要,则阻塞等待
    • doAcquireShared() 在每次循环尝试获取共享锁时,都是通过tryAcquireShared() 来尝试获取共享锁的

acquireLock

  • 因为Sync继承AQS, 所以acquireShared() 定义在AQS
  • acquireShared() 首先会通过tryAcquireShared() 来尝试获取锁:
    • 尝试成功后,直接获取锁并返回
    • 尝试失败后,通过doAcquireShared() 来循环获取锁,直到doAcquireShared() 获取到了锁之后才会返回

tryAcquireShared

  • tryAcquireShared() 定义在ReentrantReadWriteLockSync
  • tryAcquireShared() 的作用是尝试获取共享锁
    • 在尝试获取锁时,满足不需要阻塞等待并且共享读取锁的次数小于最大值MAX_COUNT(65535) 条件,直接通过CAS函数获取共享锁并更新读取锁的共享计数
    • 在尝试获取锁时,不满足以上条件,通过fullTryAcquireShared() 获取读取锁

fullTryAcquireShared

  • fullTryAcquireShared() 定义在ReentrantReadWriteLockSync
  • fullTryAcquireShared() 会根据是否需要阻塞等待以及读取锁的共享次数是否超过限制来处理线程获取读取锁
    • 在尝试获取锁时,满足不需要阻塞等待并且共享读取锁的次数小于最大值MAX_COUNT(65535) 条件,直接通过CAS函数尝试获取共享锁并更新读取锁的共享计数,返回1
    • 在尝试获取锁时,不满足以上条件,返回 -1

doAcquireShared

  • doAcquireShared() 定义在AQS类中
  • doAcquireShared() 的作用是获取共享锁:
    • 创建线程对应的CLH队列的节点
    • 将节点添加到CLH队列中 .CLH队列就是管理获取锁等待线程的等待队列
    • 如果当前线程是CLH队列的表头,就尝试获取共享锁
    • 如果当前线程不是CLH队列的表头,就通过shouldParkAfterFailedAcquire() 判断是否需要阻塞等待.如果需要阻塞等待,就通过parkAndCheckInterrupt() 进行阻塞等待
    • doAcquireShared() 通过for循环不断重复以上操作来获取共享锁 .doAcquireShared() 每一次获取共享锁的操作,都是通过tryAcquireShared() 执行的

释放共享锁Unlock

  • 释放共享锁ReadLock的过程:
    • 先通过tryReleaseShared() 尝试释放共享锁
    • 尝试成功,通过doReleaseShared() 唤醒其余等待获取共享锁的线程,并返回true
    • 尝试失败,就返回false

releaseShared

  • releaseShared() 定义在AQS类中
  • releaseShared() 的作用是使得当前线程释放持有的共享锁
    • 先通过tryReleaseShared() 尝试释放共享锁
    • 尝试成功,会通过doReleaseShared() 方法释放共享锁.唤醒距离头节点最近的并且等待状态不是CANCELLED的后继节点,然后返回true
    • 尝试失败,直接返回false

tryReleaseShared

  • tryReleaseShared() 定义在ReentrantReadWriteLockSync
  • tryReleaseShared() 的作用是尝试释放共享锁

doReleaseShared

  • doReleaseShared() 定义在AQS类中
  • doReleaseShared() 的作用是释放共享锁
    • doReleaseShared() 会从前往后遍历CLH队列,依次唤醒然后执行队列中每一个节点对应的线程
    • doReleaseShared() 的最终目的是使得这些线程释放持有的锁

公平共享锁和非公平共享锁的比较

  • ReadLock分为公平锁和非公平锁
  • 公平锁和非公平锁的区别在于判断是否需要阻塞的函数readerShouldBlock() 的区别

公平锁

final boolean readerShouldBlock() {
	return hasQueuedPredecessors();
}
  • 在公平共享锁中.如果在当前线程之前有其余的线程正在等待获取共享锁,就返回true, 否则返回false

非公平锁

final boolean readerShouldBlock() {
	return apparentlyFirstQueuedIsExclusive();
}
  • 在非公平共享锁中,无论当前线程之前是否有其余的线程正在等待获取共享锁,只要非公平共享锁对应的线程不是null, 就会返回true

以上是关于Java多线程Day23-JUC锁之共享锁的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程Day21-JUC锁之Condition条件

Java多线程Day22-JUC锁之LockSupport

Java多线程Day22-JUC锁之LockSupport

多线程编程-- part5.1 互斥锁之公平锁-获取锁

详解Java多线程锁之synchronized

JAVA 并发编程-读写锁之模拟缓存系统