Das Design und die Implementierung von Lese- / Schreibsperren in Java wissen nicht, wie man reinkommt

Für das Szenario mit mehr Lesen und weniger Schreiben bietet Java eine weitere Lese- / Schreibsperre ReentrantReadWriteLock (RRW), die die Sperrschnittstelle implementiert. Es wurde analysiert, dass ReentrantLock eine exklusive Sperre ist und nur ein Thread gleichzeitig zugreifen darf.

RRW ermöglicht den gleichzeitigen Zugriff mehrerer Reader-Threads, jedoch nicht den gleichzeitigen Zugriff des Writer-Threads und des Reader-Threads sowie des Writer-Threads und des Writer-Threads.

Die Lese- / Schreibsperre verwaltet intern zwei Sperren, eine ist ReadLock für Leseoperationen und die andere ist WriteLock für Schreiboperationen.

Lese- / Schreibsperren entsprechen den folgenden drei Grundprinzipien

Mehrere Threads
dürfen gleichzeitig gemeinsam genutzte Variablen lesen. Nur ein Thread darf gemeinsam genutzte Variablen schreiben.
Wenn ein Schreibthread eine Schreiboperation ausführt, darf der Reader-Thread derzeit keine gemeinsam genutzten Variablen lesen.

So implementieren Sie eine Lese- / Schreibsperre

RRW wird auch basierend auf AQS implementiert, und sein benutzerdefinierter Synchronisierer (von AQS geerbt) muss den Status mehrerer Reader-Threads und eines Writer-Threads im Status des Synchronisationsstatus beibehalten. Das Verfahren von RRW besteht darin, die hohen und niedrigen Bits zu verwenden, um eine Formungssteuerung für zwei Zustände zu erreichen, wobei ein int 4 Bytes und ein Byte 8 Bits belegt. Die oberen 16 Bits stehen also für Lesen und die unteren 16 Bits für Schreiben.

abstract static class Sync extends AbstractQueuedSynchronizer {

  static final int SHARED_SHIFT   = 16;

  // 10000000000000000(65536)
  static final int SHARED_UNIT    = (1 << SHARED_SHIFT);

  // 65535
  static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;

  //1111111111111111
  static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

  // 读锁(共享锁)的数量,只计算高16位的值
  static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

  // 写锁(独占锁)的数量
  static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
 }

Erwerben Sie eine Lesesperre

Wenn ein Thread eine Lesesperre erhält, beurteilt er zuerst die niedrigen 16 Bits des Synchronisationszustands. Wenn eine Schreibsperre vorhanden ist, kann er die Sperre nicht erfassen und tritt in die zu blockierende CLH-Warteschlange ein. Andernfalls wird bestimmt, ob der aktuelle Thread sollte blockiert werden. Wenn es nicht blockiert werden soll, wird der CAS-Synchronisationsstatus versucht. Die Synchronisationssperre wurde erfolgreich auf den Lesestatus aktualisiert.

protected final int tryAcquireShared(int unused) {
           
  Thread current = Thread.currentThread();
  int c = getState();
  // 如果当前已经有写锁了,则获取失败
  if (exclusiveCount(c) != 0 &&
      getExclusiveOwnerThread() != current)
      return -1;
  // 获取读锁数量
  int r = sharedCount(c);

  // 非公平锁实现中readerShouldBlock()返回true表示CLH队列中有正在排队的写锁
  // CAS设置读锁的状态值
  if (!readerShouldBlock() &&
      r < MAX_COUNT &&
      compareAndSetState(c, c + SHARED_UNIT)) {

      // 省略记录获取readLock次数的代码

      return 1;
  }

  // 针对上面失败的条件进行再次处理
  return fullTryAcquireShared(current);
}

final int fullTryAcquireShared(Thread current) {
  
  // 无线循环
  for (;;) {
    int c = getState();
    if (exclusiveCount(c) != 0) {
      // 如果不是当前线程持有写锁,则进入CLH队列阻塞
      if (getExclusiveOwnerThread() != current)
        return -1;
    } 

    // 如果reader应该被阻塞
    else if (readerShouldBlock()) {
        // 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)) {
                    rh = readHolds.get();
                    if (rh.count == 0)
                        readHolds.remove();
                }
            }
            // 当前线程没有持有读锁,即不存在锁重入情况。则进入CLH队列阻塞
            if (rh.count == 0)
                return -1;
        }
    }

    // 共享锁的如果超出了限制
    if (sharedCount(c) == MAX_COUNT)
        throw new Error("Maximum lock count exceeded");

    // CAS设置状态值
    if (compareAndSetState(c, c + SHARED_UNIT)) {
      
      // 省略记录readLock次数的代码

      return 1;
    }
  }

}

Der Wert von SHARED_UNIT ist 65536, was bedeutet, dass beim erstmaligen Erfassen der Lesesperre der Statuswert 65536 wird.

Bei der Implementierung von Fair Lock gibt die readerShouldBlock () -Methode true zurück, wenn sich Threads in der CLH-Warteschlange befinden. Wenn bei der Implementierung unfairer Sperren ein Thread darauf wartet, eine Schreibsperre in der CLH-Warteschlange zu erhalten, wird true zurückgegeben.

Es sollte auch beachtet werden, dass beim Erwerb der Lesesperre, wenn der aktuelle Thread bereits die Schreibsperre hält, die Lesesperre immer noch erfolgreich erworben werden kann. Das Downgrade der Sperre wird später erwähnt. Wenn Sie dort Fragen zum Code haben, können Sie hier auf den Code zurückblicken, um die Sperre zu beantragen.

Lösen Sie die Lesesperre

protected final boolean tryReleaseShared(int unused) {
           
  for (;;) {
    int c = getState();
    // 减去65536
    int nextc = c - SHARED_UNIT;
    // 只有当state的值变成0才会真正的释放锁
    if (compareAndSetState(c, nextc))
        return nextc == 0;
}
}

Wenn die Sperre aufgehoben wird, muss der Statuswert von 65536 subtrahiert werden, da der Wert des Status 65536 wird, wenn die Lesesperre zum ersten Mal erfasst wird.

Wenn ein Thread die Lesesperre aufhebt, wird die Sperre nur dann wirklich freigegeben, wenn state == 0 ist. Wenn beispielsweise 100 Threads die Lesesperre erhalten, führt nur der letzte Thread die tryReleaseShared-Methode aus, um die Sperre tatsächlich aufzuheben, und sie wird aktiviert zu diesem Zeitpunkt Der Thread in der Warteschlange in der CLH-Warteschlange.

Schreibsperre erwerben

Wenn ein Thread versucht, eine Schreibsperre zu erhalten, bestimmt er zuerst, ob der Synchronisationsstatus 0 ist. Wenn state gleich 0 ist, bedeutet dies, dass vorerst kein anderer Thread die Sperre erhalten hat. Wenn state ungleich 0 ist, bedeutet dies, dass andere Threads die Sperre erhalten haben.

Zu diesem Zeitpunkt wird beurteilt, ob die unteren 16 Bits (w) des Zustands 0 sind. Wenn w 0 ist, bedeutet dies, dass andere Threads die Lesesperre erhalten haben und in die CLH-Warteschlange eintreten, um das Warten zu blockieren.

Wenn w nicht 0 ist, bedeutet dies, dass andere Threads die Schreibsperre erhalten haben. Zu diesem Zeitpunkt müssen Sie feststellen, ob die Schreibsperre der aktuelle Thread ist. Wenn nicht, geben Sie die CLH-Warteschlange ein und warten Sie auf das Blockieren. Wenn die Schreibsperre ist der aktuelle Thread. Bestimmen Sie dann, ob der aktuelle Thread die Schreibsperre mehr als die maximale Anzahl von Malen erhalten hat. Wenn diese überschreitet, wird eine Ausnahme ausgelöst. Andernfalls aktualisieren Sie den Synchronisationsstatus.

// 获取写锁
protected final boolean tryAcquire(int acquires) {
           
  Thread current = Thread.currentThread();
  int c = getState();
  int w = exclusiveCount(c);

  // 判断state是否为0
  if (c != 0) {
      // 获取锁失败
      if (w == 0 || current != getExclusiveOwnerThread())
          return false;

      // 判断当前线程获取写锁是否超出了最大次数65535
      if (w + exclusiveCount(acquires) > MAX_COUNT)
          throw new Error("Maximum lock count exceeded");
      
      // 锁重入
      setState(c + acquires);
      return true;
  }
  // 非公平锁实现中writerShouldBlock()永远返回为false
  // CAS修改state的值
  if (writerShouldBlock() ||
      !compareAndSetState(c, c + acquires))
      return false;

  // CAS成功后,设置当前线程为拥有独占锁的线程
  setExclusiveOwnerThread(current);
  return true;
}

Wenn sich bei der Implementierung der fairen Sperre Threads in der CLH-Warteschlange in der Warteschlange befinden, gibt die Methode writerShouldBlock () true zurück und der Thread, der die Schreibsperre erhält, wird blockiert.

Schreibsperre aufheben

Die Logik zum Aufheben der Schreibsperre ist relativ einfach

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;
}
锁的升级?
// 准备读缓存
readLock.lock();
try {
  v = map.get(key);
  if(v == null) {
    writeLock.lock();
    try {
      if(map.get(key) != null) {
        return map.get(key);
      }

      // 更新缓存代码,省略
    } finally {
      writeLock.unlock();
    }
  }
} finally {
  readLock.unlock();
}

Für den obigen Code zum Abrufen zwischengespeicherter Daten (dies ist auch das Anwendungsszenario von RRW) erhalten Sie zuerst eine Lesesperre und anschließend ein Upgrade auf eine Schreibsperre. Dieses Verhalten wird als Sperren-Upgrade bezeichnet. Leider unterstützt RRW dies nicht. Dies führt dazu, dass die Schreibsperre für immer wartet und der Thread schließlich dauerhaft blockiert wird. Daher ist das Upgrade der Sperre nicht zulässig.

Downgrade sperren

Obwohl das Upgrade der Sperre nicht zulässig ist, ist das Downgrade der Sperre möglich.

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

ReadLock readLock = lock.readLock();

WriteLock writeLock = lock.writeLock();

Map<String, String> dataMap = new HashMap();

public void processCacheData() {
  readLock.lock();

  if(!cacheValid()) {

    // 释放读锁,因为不允许
    readLock.unlock();

    writeLock.lock();

    try {
      if(!cacheValid()) {
          dataMap.put("key", "think123");
      }

      // 降级为读锁
      readLock.lock();
    } finally {
        writeLock.unlock();
    }
  }

  try {
    // 仍然持有读锁
    System.out.println(dataMap);
  } finally {
      readLock.unlock();
  }
}

public boolean cacheValid() {
    return !dataMap.isEmpty();
}

RRW braucht Aufmerksamkeit

Bei vielen Lese- und Schreibvorgängen führt RRW dazu, dass der Schreibthread verhungert (Starvation). Dies bedeutet, dass sich der Schreibthread in einem Wartezustand befindet, da er nicht um die Sperre konkurrieren kann.
Schreibsperren unterstützen Bedingungsvariablen, Lesesperren jedoch nicht. Der Lesesperraufruf newCondition () löst eine UnsupportedOperationException aus

Ich denke du magst

Origin blog.csdn.net/doubututou/article/details/111317098
Empfohlen
Rangfolge