设为首页 加入收藏

TOP

13分钟聊聊并发包中常用同步组件并手写一个自定义同步组件(二)
2023-09-09 10:25:39 】 浏览:71
Tags:13分 钟聊聊 包中常 步组件
公平的获取同步状态

image.png

非公平锁尝试获取同步状态 流程类似就不过多描述

		final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            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;
        }

那公平锁如何来实现获取同步状态呢?

其实看过上篇AQS文章的同学就知道了,在上篇文章中已经说过

只需要在尝试获取同步状态前加上一个条件:队列中是否有前置任务(即在队列中FIFO排队获取)

公平锁也是这么去实现的,前置条件hasQueuedPredecessors

		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;
        }

ReentrantReadWriteLock

功能与实现

ReentrantReadWriteLock在ReentrantLock功能的基础上,提供读写锁的功能,让锁的粒度更细

在一些读多写少的场景下是允许同时读的,允许多个线程获取,其实想到了AQS的共享式,读锁也就是共享式

在读读的场景下,都是读锁/共享锁,不会进行阻塞

在读写、写读、写写的场景下,都会进行阻塞

比如要获取写锁时,需要等待读锁、写锁都解锁;要获取读锁时,需要等待写锁解锁

ReentrantReadWriteLock 在 ReentrantLock 的基础上增加ReadLockWriteLock分别作为读锁和写锁

image.png

实际上读锁就是共享锁、写锁就是独占锁,在实现加锁、解锁的方法时分别调用共享式、独占式的获取、释放同步状态即可

在构造时,读写锁中实际使用的都是同一个AQS

        public ReentrantReadWriteLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
            readerLock = new ReadLock(this);
            writerLock = new WriteLock(this);
        }

		//读锁构造
		protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

		//写锁构造
		protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

即同步状态会被读写锁共享,那么它们如何查看/修改自己的那部分同步状态呢?

在读写锁中,同步状态被一分为二,高16位的同步状态是读锁的,低16位的同步状态是写锁的

image.png

当线程获取写锁时,写状态+1,由于写状态在低位,相当于同步状态+1

当线程获取读锁时,读状态+1,由于读状态在高位,相当于同步状态+(1<<16)

写锁获取

写锁的获取实现在sync.tryAcquire中 sync可以是公平也可以是非公平,实际上是独占式的获取

protected final boolean tryAcquire(int acquires) {
  
    Thread current = Thread.currentThread();
    //得到同步状态c
    int c = getState();
    //得到写状态(同步状态低16位 与上 全1)
    int w = exclusiveCount(c);
    if (c != 0) {
        //同步状态不为0,写状态为0,说明读状态不为0,读锁已经被获取,此时获取写锁失败
        //同步状态不为0,写状态也不为0,查看当前线程是否是获取写锁的线程,不是的话获取写锁失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        
        //只有当前线程获取过写锁才能进入这里
        
        //如果原来的写状态+这次重入的写状态 超过了 同步状态的0~15位 则抛出异常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        
        //设置同步状态 因为写状态在低16位所以不用左移 (重入累加)
        setState(c + acquires);
        return true;
    }
    
    //同步状态为0 无锁时 
    //writerShouldBlock在非公平锁下返回false 在公平锁下查看是否有前驱任务
    //如果CAS失败则返回false
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    
    //CAS成功则 设置当前线程为获得独占锁(写锁)的线程
    setExclusiveOwnerThread(current);
    return true;
}

查看源码可以知道:

  1. 当有锁时(同步状态不为0情况),如果只有读锁(没有写锁),那么直接失败;如果只有写锁则查看当前线程是否为获取写锁的线程(重入情况)
  2. 当无锁时进行CAS获取写锁,成功则设置获取写锁的线程,失败则返回

根据源码分析可以知道,写锁允许重入,并且获取写锁时,如果有读锁会被阻塞

写锁释放

写锁的释放实现在sync.tryRelease

protected final boolean tryRelease(int releases) {
    //判断当前线程是不是获取写(独占)锁的线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    
    //新状态
    int nextc = getState() - releases;
    //如果新状态低16位为0
首页 上一页 1 2 3 4 5 下一页 尾页 2/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇MyBatis 架构与原理深入解析,面.. 下一篇Vue3实战06-CompositionAPI+<s..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目