JUC 강의 14: JUC 잠금: ReentrantReadWriteLock에 대한 자세한 설명

JUC 강의 14: JUC 잠금: ReentrantReadWriteLock에 대한 자세한 설명

이번 글은 JUC 강의 14: JUC lock-ReentrantReadWriteLock에 대한 자세한 설명입니다. ReentrantReadWriteLock은 재진입 읽기-쓰기 잠금을 나타냅니다. ReentrantReadWriteLock에는 읽기 잠금 ReadLock과 쓰기 잠금 WriteLock이라는 두 개의 잠금이 포함되어 있습니다. 스레드 간의 동기화는 이 두 잠금을 통해 달성될 수 있습니다 .

1. 주요 BAT 기업의 면접 질문을 이해하세요.

계속해서 다음 질문을 하시면 관련 지식 포인트를 더 잘 이해하는 데 도움이 될 것입니다.

  • ReentrantLock을 가지려면 ReentrantReadWriteLock이 필요합니까? 읽기 잠금 공유 및 쓰기 잠금 상호 배제를 달성하려면
  • ReentrantReadWriteLock의 기본 구현 원리는 무엇입니까? ReentrantLock + AQS
  • ReentrantReadWriteLock의 기본 읽기 및 쓰기 상태는 어떻게 설계됩니까? 상위 16비트는 읽기 잠금이고 하위 16비트는 쓰기 잠금입니다.
  • 읽기 및 쓰기 잠금의 최대 수는 얼마입니까? 2^16
  • 로컬 스레드 카운터 ThreadLocalHoldCounter의 용도는 무엇입니까?스레드는 개체와 연결됩니다.
  • 캐시 카운터 HoldCounter의 용도는 무엇입니까? 재진입 잠금 수를 기록합니다.
  • 쓰기 잠금 획득 및 해제는 어떻게 구현됩니까?
  • 읽기 잠금 획득 및 해제는 어떻게 구현됩니까?
  • 잠금 업그레이드 및 다운그레이드란 무엇입니까? RentrantReadWriteLock이 잠금 업그레이드를 지원하지 않는 이유는 무엇입니까? 가시성 확보

2. ReentrantReadWriteLock 데이터 구조

ReentrantReadWriteLock의 하위 계층은 ReentrantLock과 AbstractQueuedSynchronizer를 기반으로 구현되므로 ReentrantReadWriteLock의 데이터 구조도 AQS의 데이터 구조에 의존합니다.

3. ReentrantReadWriteLock 소스 코드 분석

3.1 클래스 상속 관계

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    
    }

참고: ReentrantReadWriteLock은 ReadWriteLock 인터페이스를 구현하는 것을 볼 수 있습니다. ReadWriteLock 인터페이스는 읽기 잠금 및 쓰기 잠금을 얻기 위한 사양을 정의합니다. 구체적으로 이를 구현하려면 구현 클래스가 필요합니다. 동시에 직렬화 가능 인터페이스도 구현합니다. 소스 코드에서 ReentrantReadWriteLock이 자체 직렬화 논리를 구현하는 것을 볼 수 있습니다.

3.2 클래스의 내부 클래스

ReentrantReadWriteLock에는 5개의 내부 클래스가 있으며 , 5개의 내부 클래스도 서로 관련되어 있습니다. 내부 클래스 간의 관계는 아래 그림과 같습니다.

img

참고: 위 그림에 표시된 것처럼 Sync는 AQS에서 상속하고 NonfairSync는 Sync 클래스에서 상속하며 FairSync는 Sync 클래스에서 상속하며 ReadLock은 Lock 인터페이스를 구현하고 WriteLock도 Lock 인터페이스를 구현합니다.

3.3 내부 클래스 - 동기화 클래스

  • 클래스 상속 관계
abstract static class Sync extends AbstractQueuedSynchronizer {
    
    }

참고: Sync 추상 클래스는 AQS 추상 클래스에서 상속되며 Sync 클래스는 ReentrantReadWriteLock에 대한 지원을 제공합니다.

  • 내부 클래스

Sync 클래스 내부에는 HoldCounter와 ThreadLocalHoldCounter라는 두 가지 내부 클래스가 있는데, HoldCounter는 주로 읽기 잠금과 함께 사용됩니다.HoldCounter의 소스 코드는 다음과 같습니다.

// 计数器
static final class HoldCounter {
    
    
    // 计数
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    // 获取当前线程的TID属性的值
    final long tid = getThreadId(Thread.currentThread());
}

참고: HoldCounter에는 주로 count와 tid라는 두 가지 속성이 있으며 여기서 count는 특정 읽기 스레드의 재진입 횟수를 나타내고 tid는 스레드의 tid 필드 값을 나타냅니다 . 이 필드는 스레드를 고유하게 식별 하는 데 사용할 수 있습니다. ThreadLocalHoldCounter의 소스코드는 다음과 같습니다.

// 本地线程计数器
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    
    
    // 重写初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值
    public HoldCounter initialValue() {
    
    
        return new HoldCounter();
    }
}

설명: ThreadLocalHoldCounter는 ThreadLocal의initialValue 메서드를 재정의합니다.ThreadLocal 클래스는 스레드를 개체와 연결할 수 있습니다. 설정이 없으면 초기 값 메서드에서 생성된 홀더카운터 개체를 얻습니다.

  • 클래스 속성
abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
    // 版本序列号
    private static final long serialVersionUID = 6317671515068378041L;
    // 高16位为读锁,低16位为写锁
    static final int SHARED_SHIFT   = 16;
    // 读锁单位  2^16
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // 读锁最大数量 2^16 - 1
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // 写锁最大数量 2^16 - 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;
}

설명: 이 속성에는 읽기 잠금 및 쓰기 잠금 스레드의 최대 수가 포함됩니다. 로컬 스레드 카운터 등

  • 클래스 생성자
// 构造函数
Sync() {
    
    
    // 本地线程计数器
    readHolds = new ThreadLocalHoldCounter();
    // 设置AQS的状态
    setState(getState()); // ensures visibility of readHolds
}

설명: 로컬 스레드 카운터와 AQS 상태는 Sync 생성자에서 설정됩니다 .

3.4 내부 클래스 - 싱크 코어 기능 분석

ReentrantReadWriteLock 개체에 대한 대부분의 작업은 처리를 위해 Sync 개체로 전달됩니다. 다음은 Sync 클래스의 주요 기능을 분석한 것이다.

  • sharedCount 함수

읽기 잠금을 보유하고 있는 스레드 수를 나타내며, 소스 코드는 다음과 같습니다.

static int sharedCount(int c)    {
    
     return c >>> SHARED_SHIFT; }

참고: 상태를 16비트 오른쪽으로 직접 이동하면 읽기 잠금 스레드 수를 얻을 수 있습니다. 상태의 상위 16비트는 읽기 잠금을 나타내고 해당 하위 16비트는 쓰기 잠금 수를 나타내기 때문입니다.

  • 배타적 카운트 함수

쓰기 잠금을 보유한 스레드 수를 나타내며 소스 코드는 다음과 같습니다.

static int exclusiveCount(int c) {
    
     return c & EXCLUSIVE_MASK; }

설명: 상태를 (2^16 - 1)로 직접 AND합니다. 이는 상태 모듈로 2^16을 변경하는 것과 같습니다. 쓰기 잠금 수는 상태의 하위 16비트로 표시됩니다.

  • tryRelease 함수
/*
* Note that tryRelease and tryAcquire can be called by
* Conditions. So it is possible that their arguments contain
* both read and write holds that are all released during a
* condition wait and re-established in tryAcquire.
*/

protected final boolean tryRelease(int releases) {
    
    
    // 判断是否为独占线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 计算释放资源后的写锁的数量
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0; // 是否释放成功
    if (free)
        setExclusiveOwnerThread(null); // 设置独占线程为空
    setState(nextc); // 设置状态
    return free;
}

설명 : 이 함수는 쓰기 잠금 리소스를 해제하는 데 사용됩니다. 먼저 스레드가 배타적 스레드인지 여부를 확인합니다. 배타적 스레드가 아닌 경우 예외가 발생합니다. 그렇지 않으면 리소스를 해제한 후 쓰기 잠금 횟수가 증가합니다. 0이면 성공, Release되면 리소스가 점유되지 않은 것이고, 그렇지 않으면 리소스가 아직 점유되어 있는 것입니다. 기능 흐름도는 다음과 같습니다.
img

  • 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();
    // 获取状态
    int c = getState();
    // 写线程数量
    int w = exclusiveCount(c);
    if (c != 0) {
    
     // 状态不为0
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread()) // 写线程数量为0或者当前线程没有占有独占资源
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT) // 判断是否超过最高写线程数量
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // 设置AQS状态
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires)) // 写线程是否应该被阻塞
        return false;
    // 设置独占线程
    setExclusiveOwnerThread(current);
    return true;
}

설명 : 이 함수는 쓰기 잠금을 획득하는데 사용되며, 먼저 상태를 획득하여 0인지 판단하고, 0이면 현재 읽기 잠금 스레드가 없음을 의미하며 이후 쓰기 스레드를 실행해야 하는지 여부를 결정합니다. 차단되지만 불공정한 정책에서는 항상 불가능하며 차단되며 공정한 정책에 따라 판단됩니다.(동기화 큐에 대기 시간이 더 긴 스레드가 있는지 판단하려면 존재하는 경우 필요합니다.) 그렇지 않으면 차단할 필요가 없습니다) 상태를 설정한 다음 true를 반환합니다. state가 0이 아니면 현재 읽기 잠금이나 쓰기 잠금 스레드가 있음을 의미하고, 쓰기 잠금 스레드 수가 0이거나 현재 스레드가 배타적 잠금 스레드이면 false를 반환하여 실패를 의미한다. 쓰기 잠금 스레드의 재진입 여부를 판단하여 횟수가 최대값보다 큰지 여부를 판단하고, 그렇다면 예외를 발생시키고, 그렇지 않으면 상태를 설정하고 true를 반환하여 성공을 나타냅니다. 그 기능 흐름도는 다음과 같습니다

img

  • tryReleaseShared 함수
protected final boolean tryReleaseShared(int unused) {
    
    
    // 获取当前线程
    Thread current = Thread.currentThread();
  	// 当前线程为第一个读线程
    if (firstReader == current) {
    
    
        // assert firstReaderHoldCount > 0;
      	// 读线程占用的资源数为1
        if (firstReaderHoldCount == 1) 
            firstReader = null;
        else // 减少占用的资源
            firstReaderHoldCount--;
    } else {
    
     // 当前线程不为第一个读线程
        // 获取缓存的计数器
        HoldCounter rh = cachedHoldCounter;
      	// 计数器为空 或者计数器的tid不为当前正在运行的线程的tid
        if (rh == null || rh.tid != getThreadId(current))
            // 获取当前线程对应的计数器
            rh = readHolds.get();
        // 获取计数
        int count = rh.count;
        if (count <= 1) {
    
     // 计数小于等于1
            // 移除
            readHolds.remove();
            if (count <= 0) // 计数小于等于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;
    }
}

설명 : 이 함수는 읽기 잠금 스레드가 잠금을 해제함을 나타냅니다 . 먼저, 현재 스레드가 첫 번째 읽기 스레드인 firstReader인지 확인하고, 그렇다면 첫 번째 읽기 스레드인 firstReaderHoldCount가 차지하는 자원의 개수가 1인지 확인하고, 그렇다면 첫 번째 읽기 스레드인 firstReader를 비어 있도록 설정하고, 그렇지 않으면 첫 번째 읽기 스레드를 빈 값으로 설정한다. 읽기 스레드 firstReader를 비우십시오. 읽기 스레드 firstReaderHoldCount가 차지하는 리소스 수는 1만큼 감소합니다. 현재 스레드가 첫 번째 읽기 스레드가 아닌 경우 캐시 카운터(이전 읽기 잠금 스레드에 해당하는 카운터)를 먼저 획득합니다. .카운터가 비어 있거나 tid가 현재 스레드의 tid 값과 같지 않으면 현재 스레드의 카운터를 가져오고, 카운터의 카운트가 1보다 작거나 같으면 현재 스레드에 해당하는 카운터를 제거합니다. 카운터의 개수가 0보다 작거나 같으면 예외를 발생시킨 다음 개수를 줄입니다. 두 경우 모두 무한 루프가 시작되어 상태가 성공적으로 설정됩니다. 흐름도는 다음과 같습니다

img

  • tryAcquireShared 함수
private IllegalMonitorStateException unmatchedUnlockException() {
    
    
    return new IllegalMonitorStateException(
        "attempt to unlock read lock, not locked by current thread");
}

// 共享模式下获取资源
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();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current) // 写线程数不为0并且占有资源的不是当前线程
        return -1;
    // 读锁数量
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
    
     // 读线程是否应该被阻塞、并且小于最大值、并且比较设置成功
        if (r == 0) {
    
     // 读锁数量为0
            // 设置第一个读线程
            firstReader = current;
            // 读线程占用的资源数为1
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
    
     // 当前线程为第一个读线程
            // 占用资源数加1
            firstReaderHoldCount++;
        } else {
    
     // 读锁数量不为0并且不为当前线程
            // 获取计数器
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current)) // 计数器为空或者计数器的tid不为当前正在运行的线程的tid
                // 获取当前线程对应的计数器
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0) // 计数为0
                // 设置
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

설명 : 이 함수는 읽기 잠금 스레드가 읽기 잠금을 획득했음을 나타냅니다. 먼저 쓰기 잠금이 0이고 현재 스레드가 배타적 잠금을 점유하고 있지 않은지 확인하고 직접 반환하고, 그렇지 않으면 읽기 스레드를 차단해야 하는지 여부와 읽기 잠금 수가 최대값보다 작은지 확인하여 비교합니다. 현재 읽기 잠금이 없으면 세 번째 A 읽기 스레드인 firstReader 및 firstReaderHoldCount를 설정하고, 현재 스레드가 첫 번째 읽기 스레드이면 firstReaderHoldCount를 늘리고, 그렇지 않은 경우 현재 스레드에 해당하는 HoldCounter 개체의 값은 다음과 같습니다. 세트. 흐름도는 다음과 같습니다.
img

  • fullTryAcquireShared 함수
final int fullTryAcquireShared(Thread current) {
    
    
    /*
        * This code is in part redundant with that in
        * tryAcquireShared but is simpler overall by not
        * complicating tryAcquireShared with interactions between
        * retries and lazily reading hold counts.
        */
    HoldCounter rh = null;
    for (;;) {
    
     // 无限循环
        // 获取状态
        int c = getState();
        if (exclusiveCount(c) != 0) {
    
     // 写线程数量不为0
            if (getExclusiveOwnerThread() != current) // 不为当前线程
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
    
     // 写线程数量为0并且读线程被阻塞
            // Make sure we're not acquiring read lock reentrantly
          	// 当前线程为第一个读线程
            if (firstReader == current) {
    
    
                // assert firstReaderHoldCount > 0;
            } else {
    
     // 当前线程不为第一个读线程
                if (rh == null) {
    
     // 计数器不为空
                    // 
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
    
     // 计数器为空或者计数器的tid不为当前正在运行的线程的tid
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT) // 读锁数量为最大值,抛出异常
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
    
     // 比较并且设置成功
            if (sharedCount(c) == 0) {
    
     // 读线程数量为0
                // 设置第一个读线程
                firstReader = current;
                // 
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
    
    
                firstReaderHoldCount++;
            } else {
    
    
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

참고: tryAcquireShared 함수에서 다음 세 가지 조건(읽기 스레드를 차단해야 하는지 여부, 최대값 미만, 비교 설정 성공)이 충족되지 않으면 fullTryAcquireShared 함수가 실행됩니다. 관련 작업이 성공할 수 있다는 것입니다. 해당 논리는 tryAcquireShared의 논리와 유사하며 더 이상 번거롭지 않습니다.

다른 내부 클래스의 작업은 기본적으로 Sync 개체에 대한 작업으로 변환되므로 여기서는 더 이상 번거롭지 않습니다.

3.5 클래스 속성

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

참고: ReentrantReadWriteLock 속성에는 읽기 잠금을 나타내는 ReentrantReadWriteLock.ReadLock 개체, 쓰기 잠금을 나타내는 ReentrantReadWriteLock.WriteLock 개체, 동기화 큐를 나타내는 Sync 개체가 포함되어 있음을 확인할 수 있습니다.

3.6 클래스 생성자

  • ReentrantReadWriteLock() 유형 생성자
public ReentrantReadWriteLock() {
    
    
  	// 默认非公平锁
    this(false);
}

설명: 이 생성자는 매개변수화된 다른 생성자를 호출합니다.

  • ReentrantReadWriteLock(boolean) 유형 생성자
public ReentrantReadWriteLock(boolean fair) {
    
    
    // 公平策略或者是非公平策略
    sync = fair ? new FairSync() : new NonfairSync();
    // 读锁
    readerLock = new ReadLock(this);
    // 写锁
    writerLock = new WriteLock(this);
}

참고: 공정한 전략이나 불공정한 전략을 지정할 수 있으며 이 생성자에서는 읽기 잠금과 쓰기 잠금이라는 두 개체가 생성됩니다.

3.7 핵심기능 분석

ReentrantReadWriteLock에 대한 작업은 기본적으로 Sync 객체에 대한 작업으로 변환되며, Sync 기능도 분석되어 더 이상 번거롭지 않습니다.

4. ReentrantReadWriteLock 예

ReentrantReadWriteLock을 사용한 예는 아래와 같으며, 소스코드는 다음과 같다.

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

실행 결과(1회):

rt1 trying to lock
rt2 trying to lock
wt1 trying to lock
rt1 lock successfully
rt2 lock successfully
rt1 unlock successfully
rt2 unlock successfully
wt1 lock successfully
wt1 unlock successfully

설명: 프로그램에서 ReentrantReadWriteLock 개체가 생성되고 읽기 스레드 2개와 쓰기 스레드 1개가 설정됩니다. 결과에 따라 다음과 같은 타이밍 다이어그램이 존재할 수 있습니다.
img

  • rt1 스레드는 rrwLock.readLock().lock 작업을 수행하며 주요 함수 호출은 다음과 같다.

img

참고: 이때 AQS 상태는 2^16입니다. 이는 현재 읽기 스레드 수가 1개라는 의미입니다 .

  • rt2 스레드는 rrwLock.readLock().lock 작업을 수행하며 주요 함수 호출은 다음과 같다.

img

참고: 이때 AQS의 상태는 2 * 2^16입니다. 이는 현재 읽기 스레드 수가 2개라는 의미입니다.

  • wt1 스레드는 rrwLock.writeLock().lock 작업을 수행하며, 주요 함수 호출은 다음과 같다.

img

참고: 현재 동기화 대기열 Sync 대기열에는 두 개의 노드가 있으며, wt1 스레드는 실행이 금지됩니다.

  • rt1 스레드는 rrwLock.readLock().unlock 작업을 수행하며 주요 함수 호출은 다음과 같다.

img

참고: 현재 AQS 상태는 2^16승이며, 이는 다른 읽기 스레드가 있음을 나타냅니다.

  • rt2 스레드는 rrwLock.readLock().unlock 작업을 수행하며, 주요 함수 호출은 다음과 같다.

img

참고: rt2 스레드가 잠금 해제 작업을 수행할 때 AQS 상태는 0이고, wt1 스레드는 언파크되어 CPU 리소스를 확보한 후 실행할 수 있습니다.

  • wt1 스레드는 CPU 리소스를 확보하고 계속 실행되며 복원되어야 합니다. 이전에 acquireQueued 함수의 parkAndCheckInterrupt 함수가 비활성화되었기 때문에 parkAndCheckInterrupt 함수로 복원하기 위한 주요 함수 호출은 다음과 같습니다.

img

참고: 마지막으로 동기화 대기열에는 노드가 하나만 있고 헤드 노드와 테일 노드가 이를 가리킵니다. AQS의 상태 값은 1이며 현재 쓰기 스레드가 있음을 나타냅니다 .

  • wt1은 rrwLock.writeLock().unlock 작업을 실행하며 주요 함수 호출은 다음과 같습니다.
    img

참고: 현재 AQS 상태는 0입니다. 이는 읽기 스레드나 쓰기 스레드가 없음을 나타냅니다. 그리고 동기화 큐 구조는 변경 사항 없이 이전 상태와 동일합니다.

프로젝트에 사용하시겠습니까?

  • 모두

5. 더 깊은 이해

5.1 잠금 업그레이드 및 다운그레이드란 무엇입니까?

잠금 다운그레이드는 쓰기 잠금이 읽기 잠금으로 다운그레이드된다는 의미입니다. 현재 스레드가 쓰기 잠금을 보유하고 있다가 이를 해제하고 최종적으로 읽기 잠금을 획득하는 경우 이 분할된 프로세스를 잠금 성능 저하라고 할 수 없습니다. 잠금 성능 저하란 쓰기 잠금(현재 소유)을 보유하고 읽기 잠금을 획득한 다음 쓰기 잠금(이전 소유)을 해제하는 프로세스를 말합니다 .

다음으로 잠금 성능 저하의 예를 살펴보세요. 데이터는 자주 변경되지 않기 때문에 여러 스레드가 동시에 데이터를 처리할 수 있는데, 데이터가 변경되면 현재 스레드가 데이터 변경을 감지하면 데이터를 준비하고 현재 스레드가 데이터 처리를 완료할 때까지 다른 처리 스레드가 차단됩니다. 코드 등의 준비작업은 다음과 같습니다.

// update变量使用volatile修饰
public void processData() {
    
    
    readLock.lock();
    if (!update) {
    
    
        // 必须先释放读锁
        readLock.unlock();
        // 锁降级从写锁获取到开始
        writeLock.lock();
        try {
    
    
            if (!update) {
    
    
                // 准备数据的流程(略)
                update = true;
            }
            readLock.lock();
        } finally {
    
    
            writeLock.unlock();
        }
        // 锁降级完成,写锁降级为读锁
    }
    try {
    
    
        // 使用数据的流程(略)
    } finally {
    
    
        readLock.unlock();
    }
}

위의 예에서는 데이터가 변경되면 업데이트 변수(Boolean 타입 및 휘발성 수정)가 false로 설정되는데, 이때 processData() 메소드에 접근하는 모든 스레드가 변경 사항을 인지할 수 있으나, 쓰기 권한을 획득할 수 있는 스레드는 단 하나뿐이다. 잠금. 다른 스레드는 읽기 잠금 및 쓰기 잠금의 lock() 메서드에서 차단됩니다. 현재 스레드는 쓰기 잠금을 획득하고 데이터 준비를 완료한 후 읽기 잠금을 획득한 다음 쓰기 잠금을 해제하여 잠금 다운그레이드를 완료합니다.

잠금 다운그레이드 시 읽기 잠금 획득이 필요한가요? 대답은 '예'입니다. 주로 데이터의 가시성을 보장하기 위해 현재 스레드가 읽기 잠금을 획득하지 않고 직접 쓰기 잠금을 해제하는 경우 다른 스레드(스레드 T로 기록됨)가 쓰기 잠금을 획득하고 데이터를 수정한다고 가정하면 현재 스레드는 이를 인식할 수 없습니다. 스레드 T. 데이터 업데이트. 현재 스레드가 읽기 잠금을 획득하면, 즉 잠금 다운그레이드 단계를 따르면 스레드 T는 현재 스레드가 데이터를 사용하고 읽기 잠금을 해제할 때까지 차단됩니다. 그런 다음 스레드 T는 데이터 업데이트를 위한 쓰기 잠금을 획득할 수 있습니다.

RentrantReadWriteLock은 잠금 업그레이드(읽기 잠금을 유지하고 쓰기 잠금을 획득한 후 최종적으로 읽기 잠금을 해제하는 프로세스)를 지원하지 않습니다. 그 목적은 데이터 가시성을 보장하는 것이기도 합니다. 여러 스레드가 읽기 잠금을 획득하고 임의의 스레드가 성공적으로 쓰기 잠금을 획득하고 데이터를 업데이트하는 경우 해당 업데이트는 읽기 잠금을 획득한 다른 스레드에 표시되지 않습니다.

6. 참고문헌

추천

출처blog.csdn.net/qq_28959087/article/details/133563647