ReentrantLockの内部原理とAQSの詳細解説

JUCにおけるAQSの構造

AQS(AbstractQueuedSynchronizer)は同期器であり、JUCの基礎ツールクラスです。ReentrantLockのロック取得はAQSを使用するため、まずAQSの全体構造を解説します。具体的な操作手法はReentrantLockの解説で説明します。

AQSのデータ構造

// AQSキュー先頭ノード
private transient volatile Node head;

// AQSキュー末尾ノード
private transient volatile Node tail;

// ロック状態: 0=未使用, >0=スレッドが保持
// state > 1 は再入を意味
private volatile int state;

// 現在排他ロックを保持するスレッド
private transient Thread exclusiveOwnerThread;

AQSのstate状態

state値はロックの占有状態を表します:

  • state = 0: ロックが解放中
  • state > 0: ロックが保持中(再入時は値増加)

スレッドはCAS操作でstateを0から1に更新しロックを取得します。失敗したスレッドはNodeにカプセル化されAQSキューで待機します。

AQSのNode構造

// ノードの待機状態
volatile int waitStatus;
// 前ノードへの参照
volatile Node prev;
// 次ノードへの参照
volatile Node next;
// ノードが保持するスレッド
volatile Thread thread;
// Conditionキュー用
Node nextWaiter;

waitStatusの主要な状態値:

static final int CANCELLED = 1;   // 要求キャンセル
static final int SIGNAL = -1;     // 後続ノードの起動が必要
static final int CONDITION = -2;  // 待機キューで待機中
static final int PROPAGATE = -3;  // 共有モード用

AQSの実装例

AQSを拡張したカスタム同期ツールの実装例:

public class CustomLock {
  private static class SyncImpl extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int arg) {
      return compareAndSetState(0, 1);
    }
    
    @Override
    protected boolean tryRelease(int arg) {
      setState(0);
      return true;
    }
    
    @Override
    protected boolean isHeldExclusively() {
      return getState() == 1;
    }
  }
  
  private final SyncImpl sync = new SyncImpl();
  
  public void acquireLock() {
    sync.acquire(1);
  }
  
  public void releaseLock() {
    sync.release(1);
  }
}

使用例:

public class LockExample {
  static int total = 0;
  static CustomLock customLock = new CustomLock();
  
  public static void main(String[] args) throws InterruptedException {
    Runnable task = () -> {
      customLock.acquireLock();
      try {
        for(int i=0; i<10000; i++) total++;
      } finally {
        customLock.releaseLock();
      }
    };
    
    Thread t1 = new Thread(task);
    Thread t2 = new Thread(task);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(total);
  }
}

ReentrantLockの深層原理

ReentrantLockのロック機構はAQSを基盤としています。ロック取得プロセスから解析します。

ReentrantLockの構成

public class ReentrantLockDemo {
  public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();
    lock.lock();
  }
}

コンストラクタの挙動:

// デフォルト:非公平ロック
public ReentrantLock() {
  sync = new NonfairSync();
}

// 公平性選択可能
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

sync変数はSyncクラスのインスタンスで、AQSを継承しています。FairSyncとNonfairSyncはSyncのサブクラスです。

非公平ロック取得原理

非公平ロックの取得プロセス:

final void lock() {
  if (compareAndSetState(0, 1))
    setExclusiveOwnerThread(Thread.currentThread());

タグ: Java ReentrantLock AQS スレッド同期 排他制御

8月13日 08:13 投稿