首页 > 编程语言 >第 6 章 Java 并发包中锁原理剖析Part two

第 6 章 Java 并发包中锁原理剖析Part two

时间:2024-12-10 21:28:56浏览次数:8  
标签:int 中锁 写锁 void two 获取 读锁 线程 发包

目录

6.3 读写锁 ReentrantReadWriteLock 的原理

写锁的获取与释放 

1.void lock()

 2.void lockInterruptibly()

3.boolean tryLock()

4.boolean tryLock(long timeout, TimeUnit unit)

5.释放锁 void unlock()

读锁的获取与释放

1.void lock()

2.void lockInterruptibly()

3.boolean tryLock()

4.boolean tryLock(long timeout, TimeUnit unit)

5.void unlock()


6.3 读写锁 ReentrantReadWriteLock 的原理

解决线程安全问题使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,某时只 有一个线程可以获取该锁,而实际中会有写少读多的场景,显然 ReentrantLock 满足不了 这个需求,所以 ReentrantReadWriteLock 应运而生。 ReentrantReadWriteLock 采用读写分 离的策略,允许多个线程可以同时获取读锁。
读写锁的内部维护了一个 ReadLock 和一个 WriteLock ,它们依赖 Sync 实现具体功能。 而 Sync 继承自 AQS,并且也提供了公平和非公平的实现。[这里说的是非公平锁],我们知道 AQS 中只维护了一个 state 状态,而 ReentrantReadWriteLock 则需要维护读状态和写状态,一个 state 怎么表示写和读两种状态呢 ?

ReentrantReadWriteLock 巧妙地使用 state 的16 位表示读状态,也就是获取到读锁的次数 ;使用16 位表示获取到写锁的线程的可重入次数

  • firstReader 用来记录第一个获取到读锁的线程
  • firstReaderHoldCount 则记录第一个获取到读锁的线程获取读锁的可重入次数
  • cachedHoldCounter 用来记录最后一个获取读锁的线程获取读锁的可重入次数
  • readHolds 是 ThreadLocal 变量,用来存放除去第一个获取读锁线程外的其他线程获取 读锁的可重入次数
static final int SHARED_SHIFT = 16;

//共享锁(读锁)状态单位值65536 
static final int SHARED_UNIT = (1 << SHARED_SHIFT);

//共享锁线程最大个数65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;

//排它锁(写锁)掩码, 二进制 ,15个1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

/** 返回读锁线程数 */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** 返回写锁可重入个数 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

static final class HoldCounter {
 int count = 0;
 //线程id
 final long tid = getThreadId(Thread.currentThread());
 }

 static final class ThreadLocalHoldCounter
 extends ThreadLocal<HoldCounter> {
 public HoldCounter initialValue() {
 return new HoldCounter();
     }
 }

写锁的获取与释放 

1.void lock()

写锁是个独占锁,某时只有一个线程可以获取该锁。 如果当前没有线程获取到读锁和写锁 , 则当前线程可以获取到写锁然后返回。 如果当前已经有线程获取到读锁和写锁 , 则当前请求写锁的线程会被阻塞挂起。 另外,写锁是可重入锁,如果当前线程已经获取了该锁,再次获取只是简单地把可重入次数加 1 后直接返回。重写的tryAcquire方法
public void lock() {
 sync.acquire(1);
 }
 
 public final void acquire(int arg) {

 // sync重写的tryAcquire方法
 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    
     selfInterrupt();
 }

//tryAcquire方法
protected final boolean tryAcquire(int acquires) {
 
 Thread current = Thread.currentThread();
 int c = getState();
 int w = exclusiveCount(c);

     //(1) c!=0说明读锁或者写锁已经被某线程获取
 if (c != 0) {

     //(2)w=0说明已经有线程获取了读锁,w!=0并且当前线程不是写锁拥有者,则返回false
 if (w == 0 || current != getExclusiveOwnerThread())
 return false;

     //(3)说明当前线程获取了写锁,判断可重入次数
 if (w + exclusiveCount(acquires) > MAX_COUNT) 
throw  new Error("Maximum lock count exceeded");
 
     // (4)设置可重入次数(1)
 setState(c + acquires);

 return true;
 }
// c == 0 则说明目前没有线程获取到读锁和写锁
    //(5)第一个写线程获取写锁
 if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
     return false; //对于非公平锁来说总是返回 false

     setExclusiveOwnerThread(current);

 return true;
 }

 2.void lockInterruptibly()

类似于 lock() 方法,它的不同之处在于,它会对中断进行响应,也就是当其他线程调用了该线程的 interrupt() 方法中断了当前线程时,当前线程会抛出异常 InterruptedException 异常。
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

3.boolean tryLock()

尝试获取写锁,如果当前没有其他线程持有写锁或者读锁,则当前线程获取写锁会成功,然后返回 true 。 如果当前已经有其他线程持有写锁或者读锁则该方法直接返回 false ,且当前线程并不会被阻塞。 如果当前线程已经持有了该写锁则简单增加 AQS 的状态值后直接返回 true 。
public boolean tryLock( ) {
    return sync.tryWriteLock();
}

final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c != 0) {
        int w = exclusiveCount(c);
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

4.boolean tryLock(long timeout, TimeUnit unit)

与 tryAcquire ()的不同之处在于,多了超时时间参数,如果尝试获取写锁失败则会把当前线程挂起指定时间,待超时时间到后当前线程被激活,如果还是没有获取到写锁则返回 false 。另外,该方法会对中断进行响应 , 也就是当其他线程调用了该线程的 interrupt() 方法中断了当前线程时,当前线程会抛出 InterruptedException 异常。
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {

return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

5.释放锁 void unlock()

尝试释放锁,如果当前线程持有该锁,调用该方法会让该线程对该线程持有的 AQS 状态值减 1 ,如果减去 1 后当前状态值为 0 则当前线程会释放该锁,否则仅仅减 1 而已。如果当前线程没有持有该锁而调用了该方法则会抛出 IllegalMonitorStateException 异常 
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
//调用ReentrantReadWriteLock中sync实现的tryRelease方法
    if (tryRelease(arg)) {
//激活阻塞队列里面的一个线程
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
//(6) 看是否是写锁拥有者调用的unlock
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
//(7)获取可重入值,这里没有考虑高16位,因为获取写锁时读锁状态值肯定为0
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
//(8)如果写锁可重入值为0则释放锁,否则只是简单地更新状态值
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

读锁的获取与释放

1.void lock()

获取读锁,如果当前没有其他线程持有写锁,则当前线程可以获取读锁,AQS 的状态值 state 的高 16 位的值会增加 1,然后方法返回。否则如果其他一个线程持有写锁,则当前线程会被阻塞。

读锁的 lock 方法调用了 AQS 的 acquireShared 方法,在其内部调用了 ReentrantReadWriteLock 中的 sync 重写的 tryAcquireShared 方法
public void lock() {
    sync.acquireShared(1);
}

public final void acquireShared(int arg) {
//调用ReentrantReadWriteLock中的sync的tryAcquireShared方法
    if (tryAcquireShared(arg) < 0)
//调用AQS的doAcquireShared方法
        doAcquireShared(arg);
}

protected final int tryAcquireShared(int unused) {

    //(1)获取当前状态值
    Thread current = Thread.currentThread();
    int c = getState();
    //(2)判断是否写锁被占用
    if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
        return -1;
    //(3)获取读锁计数
    int r = sharedCount(c);
    //(4)尝试获取锁,多个读线程只有一个会成功,不成功的进入fullTryAcquireShared进行重试
    if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) {
        //(5)第一个线程获取读锁
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
            //(6)如果当前线程是第一个获取读锁的线程
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            //(7)记录最后一个获取读锁的线程或记录其他线程读锁的可重入数
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != current.getId())
            cachedHoldCounter = rh = readHolds.get();
 else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    //(8)类似tryAcquireShared,但是是自旋获取
    return fullTryAcquireShared(current);
}

2.void lockInterruptibly()

类似于 lock() 方法,不同之处在于,该方法会对中断进行响应,也就是当其他线程调用了该线程的interrupt() 方法中断了当前线程时,当前线程会抛出 InterruptedException 异常。

3.boolean tryLock()

尝试获取读锁,如果当前没有其他线程持有写锁,则当前线程获取读锁会成功,然后返回 true 。如果当前已经有其他线程持有写锁则该方法直接返回 false ,但当前线程并不会被阻塞。 如果当前线程已经持有了该读锁则简单增加 AQS 的状态值高 16 位后直接返回 true 。其代码类似 tryLock 的代码

4.boolean tryLock(long timeout, TimeUnit unit)

与 tryLock ()的不同之处在于,多了超时时间参数,如果尝试获取读锁失败则会把当前线程挂起指定时间,待超时时间到后当前线程被激活,如果此时还没有获取到读锁则返回 false 。另外,该方法对中断响应 , 也就是当其他线程调用了该线程的 interrupt() 方法中断了当前线程时,当前线程会抛出 InterruptedException 异常。

5.void unlock()

释放锁的操作是委托给 Sync 类来做的,调用sync.releaseShared 方法

public void unlock() {
    sync.releaseShared(1);
}

标签:int,中锁,写锁,void,two,获取,读锁,线程,发包
From: https://blog.csdn.net/liiilbb/article/details/144332752

相关文章

  • 最高法--发包人未尽到通知义务,但案涉事实可证实质量问题责任方且发包人对于不让承包人
    (2023)最高法民申1043号  某公司1、某公司2建设工程施工合同纠纷民事申请再审审查申请人主张: (三)根据《最高人民法院关于审理建设工程施工合同纠纷案件适用法律问题的解释》(法释[2004]14号)第十三条的规定,某甲公司的保修义务已经免除。即便某甲公司承担工程质量保修责任,也仅对......
  • github代码修改指南|乳腺超声肿块分割代码项目|Global Guidance Network for Breast L
    目录此项目相关信息显而易见的错误文件缺失很容易失误的地方此项目相关信息github链接:https://github.com/xorangecheng/GlobalGuidance-Net论文链接(2021MIA论文):https://www.sciencedirect.com/science/article/pii/S1361841521000359github提供的代码质量真的感人,我改了一下......
  • 【AI学习笔记3】神经元、神经网路与感知机 Neuron、Neural Network and Perceptron
    一、从生物神经元到人工神经网络    每个神经元细胞都向外伸出许多分支,其中用来接收输入的分支称作树突(dendrites),用来输出信号的分支称作轴突(axon),轴突连接到树突上形成一个突触(synapse)。每个神经元可以通过这种方式连接多个其他神经元,每个神经元也可以接受多个其他......
  • 第 6 章 Java 并发包中锁原理剖析Part one
    目录6.1LockSupport工具类 6.2独占锁ReentrantLock的原理 获取锁 1.voidlock()方法2.voidlockInterruptibly()方法3.booleantryLock()方法4.booleantryLock(longtimeout,TimeUnitunit)方法释放锁6.1LockSupport工具类 LockSupport它的主......
  • AEC论文解读 -- ACOUSTIC ECHO CANCELLATION WITH THE DUAL-SIGNAL TRANSFORMATION LS
    程序地址预训练模型一、技术解读1.1信号处理1.1.1数据集来源合成数据集:包含10,000个示例,涵盖单工、双工、近端噪声、远端噪声和非线性失真情况。真实录音数据集:包含不同环境中的录音,确保多样性。前500个示例用于工具评估,称为“双工测试集”。训练时仅使用远端信......
  • 论文总结-经典论文-联邦学习/Communication-Efficient Learning of Deep Networks fro
    本文为《基于分布式数据的深度网络高效通信学习》的总结和原文翻译稿,原文由H.BrendanMcMahan等人(Google团队)发表于ComputingResearchRepository(CoRR)(2016)。该论文首次提出“联邦学习”(FederatedLearning)的概念,解决的问题为保护用户数据隐私和避免合规风险。具体而言,移......
  • 《Boundary-induced and Scene-aggregated Network for Monocular Depth Prediction》
    Boundary-inducedandScene-aggregatedNetworkforMonocularDepthPrediction这篇论文主要是做有监督深度估计,这里重点看了一下他的创新点和损失函数创新点针对创新点,主要遇到的一个问题是深度估计边缘不清晰,边缘处深度估计不准确BUBF自底向上的边界融合模块每一层编码器......
  • chrome 替换network中的返回内容,用以跨步调试
    在开发调试中,有时候,某个接口,或者文件返回内容有问题,但线上的文件没问题。这时候就可以通过更改network中返回内容来实现跨步调试了。test.html<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=devi......
  • COMP09110 Python for Network Engineers
    FINALCOURSEWORKINSTRUCTIONS[50%offinalmark]COMP09110PythonforNetworkEngineersThistaskisdesignedtotesttheabilitytoputintopracticetheknowledgegainedduringyourmodule.Yourtaskwillbetocreateasimplevideogameserverthats......
  • [Design Pattern] Encapsulate a network request lib - 4. API Template
    Whencompany'sAPIbecomehugeandalwayschanging,if request-busismaintainedbydevelopersmanually,it'snotonlytimeconsumingbutalsoerrorprone.Wecanintroducesomestandardautomationprocesstoresolvetheproblem. Examples:{......