Javaのsynchronizedの解説と典型的誤り集(記事の誤りや意見等あれば是非教えてください)

# synchronizedの基本
プログラムにてスレッドを分けて処理しているけれど、複数スレッドに同時に処理を行わせてはいけない箇所(**クリティカルセクション**)があり、そこでは処理実行可能なスレッドを制限したいときなどに使う。
synchronizedと書けばいつも内部がシングルスレッド処理になるというものではないので、**何のインスタンスを使って領域を守っているか**をしっかり意識すること。

## synchronizedメソッド
synchronizedメソッドは、そのインスタンスにおいてsynchronized記述されているメソッドを実行できるスレッドを1つだけに制限するためのもの。
以下のクラスにおいて、`someMethod()`および`anotherMethod()`はsynchronizedメソッドであり、このsomeClassインスタンスに対して複数のスレッドが`someMethod()`や`anotherMethod()`を呼び出しても、一度に処理を行えるのは1スレッドだけである。

```java
class SomeClass {
  synchronized void someMethod() {
    doSomething(); // ここに来れるのは1スレッドだけ
  }

  synchronized void anotherMethod() {
    doAnotherthing(); // ここに来れるのは1スレッドだけ
  }
  ...
}
```



## synchronizedブロック
synchronizedブロックは、特定のコードブロックの実行を1スレッドに制限するためのもの。以下のような形式で記載する。

```java
synchronized(instance) {
  ... // ここの処理が行えるのは、上記instanceをロックとして取得できた1スレッドのみ
}
```

複数の箇所で同じ同期インスタンスに対してsynchronizedブロックが記述されている場合は、複数のコードブロックにまたがって1スレッド制限がかかる。

```java
void someMethod() {
  synchronized(instance) {
    ... // ここの処理を行っているスレッドがあると
  }
}

void anotherMethod() {
  synchronized(instance) {
    ... // ここの処理を他スレッドが行うことはできない(実行を待たされる)。
  }
}
```

synchronizedメソッドは、メソッド内部の処理全体をsynchronzedブロックで囲って、thisを使って同期しているのと等価と考えてよい[^1]。
[^1]: VMの実装によって実行時間は異なる可能性がある。
つまり、以下のメソッドは、

```java
synchronized void someMethod() {
  doSomething();
}
```

以下のメソッドと等価である[^1]。

```java
void someMethod() {
  synchronized (this) {
    doSomething();
  }
}
```


# synchronizedブロックで使用する同期インスタンスと典型的誤り
synchronizedブロックでよく用いられる同期インスタンスと典型的誤りを記す。

## this
以下のコードで、`doSomething()`と`doAnotherthing()`は、ともに**this**を使ったsynchronizedブロックで囲まれているため、このSomeClassインスタンスに対して`doSomething()`もしくは`doAnotherthing()`を実行できるスレッドは一つだけになる。

```java
class SomeClass() {
  void someMethod() {
    synchronized (this) {
      doSomething(); // ある時点でdoSomething()もしくはdoAnotherthing()を実行できるスレッドは一つだけ
    }
  }

  void anotherMethod() {
    synchronized (this) {
      doAnotherthing(); // ある時点でdoSomething()もしくはdoAnotherthing()を実行できるスレッドは一つだけ
    }
  }
  
  private void doSomething() {
    ...
  }

  private void doAnotherthing() {
    ...
  }

}
```

### 典型的誤り：他のSomeClassインスタンスに対する同期
同期インスタンスは**this**のため、`new SomeClass()`が複数回行われるなどして複数のインスタンスが生成された場合、ぞれぞれのインスタンスにてthisは別物である。ゆえに、**SomeClassインスタンス間でのスレッドの制限は行われない**。

```java
// 以外のコードはスレッド制御かからない
(new SomeClass()).someMethod();
(new SomeClass()).anotherMethod();
```

これ以降の内容は`new SomeClass()`が一度だけしか行われていない前提で記載する。

## mLock等のプライベート変数
以下では、**mLock1**と**mLock2**の二つの同期インスタンスを生成している。
mLock1にて守られているので、`doSomething()`を実行できるスレッドは一つだけ。同様に、mLock2にて守られているので、`doAnotherthing()`を実行できるスレッドも一つだけ。`doSomething()`と`doAnotherthing()`の間では排他は行われない。

```java
class SomeClass() {
  private Object mLock1 = new Object();
  private Object mLock2 = new Object();

  void someMethod() {
    synchronized (mLock1) {
      doSomething();  // ある時点でdoSomething()を実行できるスレッドは一つだけ
    }
  }

  void anotherMethod() {
    synchronized (mLock2) {
      doAnotherthing(); // ある時点でdoAnotherthing()を実行できるスレッドは一つだけ
    }
  }
  
  private void doSomething() {
    ...
  }

  private void doAnotherthing() {
    ...
  }
}
```

### 典型的誤り：無意味なロックインスタンス生成
以下では、**mLock**というインスタンスを生成して同期インスタンスに使用している。
基本的には、**mLock**を定義せず、**this**を使ってロックすれば良いという場面が多い。例外についてはサンプルコードの下の記述を参照のこと。

```java
class SomeClass() {
  private Object mLock = new Object(); // 定義しなくてもthisで良い

  void someMethod() {
    synchronized (mLock) {
      doSomething();
    }
  }

  void anotherMethod() {
    synchronized (mLock) {
      doAnotherthing();
    }
  }
  ...
}
```


##### コメント踏まえて加筆
@sdkeiさんに教えてもらったが、thisは他クラスからも参照可能なので、無作法な利用者にロックに利用される可能性がある。それを防ぐためにprivateなmLockを定義して使用する場合がある。synchronizedメソッドにも同様の危険性がある。
95%くらいはthisを使えば良いと気づいていないケースだと思うのでこの誤りパターンは消さないが、周辺のメソッドやクラスに**final修飾子**が記載されているようなしっかりプロジェクトでは、明確な意思のもとでmLockを定義していると場合が多いだろう。

### 典型的誤り：デッドロック
以下において、`someMethod()`および`anotherMethod()`が異なるスレッドで呼ばれるとデッドロックする可能性がある。特に`doSomething()`の処理時間が長い場合はすぐに発生する。

```java
class SomeClass() {
  private Object mLock1 = new Object();
  private Object mLock2 = new Object();

  void someMethod() {
    synchronized(mLock1) { // mLock1を取得する
      doSomething();
      synchronized(mLock2) { //  mLock2の取得をしようとする
        doAnotherthing();
      }
    }
  }

  void anotherMethod() {
    synchronized(mLock2) { // mLock2を取得する
      doSomething();
      synchronized(mLock1) { // mLock1を取得しようとする *デッドロック
        doAnotherthing();
      }
    }
  }
  ...
}
```


### 典型的誤り：不十分な保護
以下において、`anotherMethod()`呼び出しにより**mLock**インスタンスが書き換わると、`doSomething()`が複数スレッドから実行可能になる。

```java
class SomeClass() {
  private Object mLock = new Object();

  void someMethod() {
    synchronized(mLock) {
      doSomething();
    }
  }

  void anotherMethod() {
    mLock = new Object(); // mLockが他インスタンスになる
  }
  ...
}
```



### 典型的誤り：無意味な保護
以下において、最初のsynchronizedブロックで**mLock**をすでに取得しているので、二つ目のsynchronizedブロックには意味がない。たぶん**開発者の意図した動作になっていない**と思うので、このコードを見つけた時には真意を聞いたほうがいい。

```java
class SomeClass() {
  private Object mLock = new Object();

  void method() {
    synchronized(mLock) { // mLockを取得する
      someMethod();
      synchronized(mLock) { // mLockを取得済みなので意味がない
        otherMethod();
      }
    }
  }
}
```



## CLASS_NAME.class
以下では、**SomeClass.class**というインスタンスを使って同期を行っている。VM上でSomeClassだけに作られた唯一のインスタンス(`Class<SomeClass>`インスタンス)を参照するため、**VM上で単一**であることが保証される。
かなり強いロックになるので、ロック取得と放棄のコストを考えると**シングルスレッド実行のほうが処理速度早い**なんてことにもなる。

```java
class SomeClass() {
  void someMethod() {
    synchronized (SomeClass.class) {
      doSomething();
    }
  }

  void anotherMethod() {
    synchronized (SomeClass.class) {
      doAnotherthing();
    }
  }
  ...
}
```

  

# その他の典型的誤り
synchronizedではないが、同じ同期カテゴリということで以下のような部分にも誤りが多いので記載しておく。

### 誤り：プリミティブ型変数への代入はアトミック操作
longとdouble以外のプリミティブ型変数への値の代入はアトミックなので誤解しがちだが、**longとdoubleへ値の代入はアトミックではない**。

以下はスレッドセーフだが、

```java
class SomeClass {
  private int mValue = 0;

  void assign(int value) {
    mValue = value;
  }
}
```

以下はスレッドセーフではない。

```java
class SomeClass {
  private long mValue = 0;

  void assign(long value) {
    mValue = value;
  }
}
```

### 誤り：volatile修飾子をつけた変数への操作はアトミック操作
変数xに対してスレッドAで書き込みを行った後にスレッドBで読み込みを行った際、xの最新の値が返ってくるとは限らない。**変数定義の際にvolatileを付けておくと、最新の値が返ってくる**ことが保証される。

volatileを付けておくとindex++等の処理がアトミックになると記載しているサイトがあるが、これは誤りである。[TECHSCOREの記載](https://www.techscore.com/tech/Java/JavaSE/Thread/3-3/#thr3-5)も、volatileを付けていても`a=0, b=1`となることはあるので誤りである。

# 代替案
synchronizedを使わなくても、もっと軽量にスレッドセーフにすることができる場合があるので紹介する。

### 値の加算や除算をスレッドセーフに行う
`java.util.concurrent.atomic`に定義されたクラスを使う。
例えば、`AtomicInteger`には`incrementAndGet()`や`addAndGet(int delta)`などのメソッドがあり、スレッドセーフにインクリメントや加算・除算を行うことが出来る。

### 書き込み中の読み書きはロックしたいが、そうでない場合は読み込みを許可したい
以下のような場合は、

- 値の書き込みを行っているスレッドがある場合、他スレッドの書き込みと読み込みをブロックしたい
- 書き込みが行われていないときは読みこみをブロックしたくない

以下のように、値の`get()`も`add()`もsynchronizedで囲ってしまうのではなく、

```java
class DataStorage() {
  private List<Integer> mStorage = new ArrayList<>();

  synchronized Integer get(int index) {
    return mStorage.get(index);
  }

  synchronized boolean add(Integer value) {
    return mStorage.add(value);
  }

```

以下のように`ReentrantReadWriteLocks`を使うことで、`get()`同士はブロックしないが`add()`を処理中には`add()`と`get()`をブロックするというようなことができる。


```java
class DataStorage() {
  private List<Integer> mStorage = new ArrayList<>();
  private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  private Lock r = rwl.readLock();
  private Lock w = rwl.writeLock();

  synchronized Integer get(int index) {
    r.lock(); // read lock
    try {
      return mStorage.get(index);
    } finally {
      r.unlock(); // read unlock
    }
  }

  synchronized boolean add(Integer value) {
    w.lock(); // write lock
    try {
      return mStorage.add(value);
    } finally {
      w.unlock(); // write unlock
    }
  }

```

この例だとCopyOnWriteArrayListを使ったほうがいいような気もするが。

#### ConcurrentModificationExceptionを防ぎたい場合
`CopyOnWriteArrayList`等の`java.util.concurrent`パッケージに定義されたクラスを使う。

# その他のロック
`Semaphore`や`CountDownLatch`等あり、便利なので以下を参照のこと。
[Javaの排他制御(ロック)に関係するクラスまとめ](https://qiita.com/takezoux2/items/7560c5151b58f1bd2c6f)