【Java Gold】synchronizedとReentrantLockの整理

## はじめに
`synchronized`と`ReentrantLock`の使い分けに不安があったため、ここで基本を整理します。


## synchronizedの基本

### 排他制御とは？

複数のスレッドが同じ処理・データに**同時にアクセスしないようにする仕組み**のことです。
Javaでは`synchronized`を使うことで、簡単に排他制御を行えます。


## synchronizedの使い方

### メソッドに指定（インスタンスメソッド）

```java
public synchronized void increment() {
    count++;
}
```

* このメソッドはインスタンス単位で排他制御されます（`this`にロックがかかる）。

### staticメソッドに指定（クラスロック）

```java
public static synchronized void log(String msg) {
    // クラス全体に対してロック
}
```

* クラス全体にロック（`クラス名.class`）がかかります。
* 全インスタンス共通のデータを扱うときに使います。

### ブロックに指定（より細かく制御）

```java
public void add(int value) {
    synchronized(this) {
        list.add(value);
    }
}
```

```java
public void add(int value) {
    synchronized(lockObj) {
        list.add(value);
    }
}
```

* 処理の一部にだけロックをかけたいときに有効です。


## synchronizedの動作原理

* `synchronized`ブロックに入るときに、対象オブジェクトのモニタロックを取得
* 処理が終わるとロックを自動的に解放
* 他スレッドはロックが解放されるまで待機


## synchronizedの注意点

### ロック対象の選び方に注意

| ロック対象                                       | 説明               | 注意点           |
| ------------------------------------------- | ---------------- | ------------- |
| `this`                                      | インスタンス単位の排他制御    | インスタンスが異なれば無効 |
| `MyClass.class`                             | クラス単位の排他制御       | staticデータ用    |
| `new Object()`                              | 毎回異なるため意味なし      | ❌ 無意味なロックになる  |
| `Integer`や`String`などのラッパー                   | 変更不可かつキャッシュ特性がある | ❌ 予期せぬ共有の危険あり |
| `private final Object lock = new Object();` | 推奨される方法          | 安定した排他制御が可能   |


### ロック対象が途中で変わると無効

```java
synchronized(lockObj.num) { ... }  // ❌ numが変更されると別オブジェクトに
```

* ロック対象は`final`で固定すべき


### ロックの粒度

| 粒度         | メリット       | デメリット        |
| ---------- | ---------- | ------------ |
| 広い（メソッド全体） | コードが簡単     | 同時実行性が低くなる   |
| 狭い（必要最小限）  | 同時実行性が高くなる | コードが複雑になりやすい |

* 原則として「必要な範囲のみ」をロックするのがベスト


### デッドロックに注意

* 複数ロックを異なる順序で取得すると、デッドロック（相互待機）になる
* 対策：

  * ロック取得の順番を常に統一する
  * `ReentrantLock`の`tryLock`でタイムアウト制御


### 再入可能性

`synchronized`は再入可能です。
同一スレッドが再度同じロックを取得することができます。

```java
public synchronized void methodA() {
    methodB();  // 同じロックを保持しているためOK
}
```


### スレッド競合のある例と解決

```java
public class Counter {
    private int count = 0;

    public void increment() {
        count++;  // スレッドセーフでない
    }

    public int getCount() {
        return count;
    }
}
```

上記はスレッドセーフではありません。
`synchronized`を使って以下のように修正します。

```java
public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}
```

## ReentrantLockとは

`java.util.concurrent.locks.ReentrantLock`は、`synchronized`と同様の排他制御を**明示的に制御**できるクラスです。

* 再入可能（同じスレッドが複数回ロック取得可能）
* ロック取得/解放を手動で管理するため、細かい制御が可能


## ReentrantLockの使い方

```java
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        return count;
    }
}
```

* `lock()`でロックを取得し、必ず`unlock()`で解放（`finally`ブロックで書くのが鉄則）


## ReentrantLockのインスタンス生成場所

### インスタンスフィールドとして定義

```java
private final ReentrantLock lock = new ReentrantLock();
```

* 同じオブジェクト内の複数メソッドで共有可能
* 一番一般的かつ安全なパターン

### staticフィールドに定義（クラス全体で共有）

```java
private static final ReentrantLock lock = new ReentrantLock();
```

* 全インスタンスで共有されるロック（staticフィールドやstaticメソッドを守るとき）

### メソッド内で`new`

```java
public void increment() {
    ReentrantLock lock = new ReentrantLock();  // ❌ 無意味
}
```

* 毎回異なるロックになるため、排他制御が意味をなさない

### 外部から注入する

```java
public class Service {
    private final ReentrantLock lock;

    public Service(ReentrantLock lock) {
        this.lock = lock;
    }
}
```

* 柔軟な設計や複数オブジェクトで同じロックを使いたい場合に有効

---

## synchronizedとReentrantLockの比較

| 比較項目    | synchronized | ReentrantLock               |
| ------- | ------------ | --------------------------- |
| ロックの種類  | 暗黙的（モニタロック）  | 明示的（コードで管理）                 |
| ロック管理   | 自動           | 手動（lock/unlock）             |
| tryLock | 不可           | 可能（ロック失敗時の分岐が可能）            |
| 割り込み対応  | 不可           | `lockInterruptibly()`で可能    |
| 公平性制御   | 不可           | コンストラクタで設定可能（先に待っていたスレッド優先） |


## まとめ

* `synchronized`はシンプルで安全、ロックが短時間かつスコープが明確なときに便利
* `ReentrantLock`は細かな制御や、割り込み・タイムアウト処理をしたいときに最適