スレッドセーフなメソッドを並行処理を安全に行うことができないメソッドでオーバーライドすると、同期処理が正しく行われない可能性がある。たとえば、サブクラスでそのようなオーバーライドを行っており、親クラスのスレッド安全性に依存したクライアントがサブクラスのインスタンスに対して操作を行った場合、このような問題が発生する。そのようなオーバーライドは分析困難なエラーにつながりやすい。そのため、プログラムでは、スレッドセーフなメソッドをスレッドセーフでないメソッドでオーバーライドしてはならない。
継承されることを前提に設計されたクラスのロック方法は、文書化しておくべきである。そうすれば、サブクラスではその情報をもとに、適切なロック方法を実装することができる(「LCK00-J. 信頼できないコードから使用されるクラスを同期するにはprivate finalロックオブジェクトを使用する」および 「LCK11-J. 一貫したロック方式を定めていないクラスを使用する場合、クライアントサイドロックを行わない」を参照)。
違反コード (synchronized メソッド)
以下の違反コード例では、Base
クラスの中の synchronized 修飾された doSomething()
メソッドを、Derived
サブクラスの synchronized 修飾されていないメソッドでオーバーライドしている。
class Base {
public synchronized void doSomething() {
// ...
}
}
class Derived extends Base {
@Override public void doSomething() {
// ...
}
}
Base
クラスの doSomething()
メソッドは、複数のスレッドが実行しても安全であるが、Derived
サブクラスのインスタンスの doSomething()
メソッドを複数のスレッドが実行するのは安全ではない。
Base
クラスのインスタンスを扱うスレッドは、そのサブクラスである Derived
クラスのインスタンスも扱えるため、このようなプログラミングエラーは分析が困難であろう。クライアント側プログラムでは、スレッドセーフなクラスのサブクラスのインスタンスを、スレッドセーフではないと知らずに使用してしまうかもしれない。
適合コード (synchronized メソッド)
以下の適合コードでは、サブクラスの doSomething()
メソッドを synchronized 修飾している。
class Base {
public synchronized void doSomething() {
// ...
}
}
class Derived extends Base {
@Override public synchronized void doSomething() {
// ...
}
}
この適合コードでは、クラスのアクセス範囲がパッケージプライベートであるため、「LCK00-J. 信頼できないコードから使用されるクラスを同期するにはprivate finalロックオブジェクトを使用する」にも適合している。信頼できないコードが、該当するパッケージにアクセスできないのであれば、アクセス範囲をパッケージプライベートにしてもよい。
適合コード (private final ロックオブジェクト)
以下の適合コードでは、Base
クラスの synchronized 修飾された doSomething()
メソッドを、private final 宣言されたロックオブジェクトを使って同期するメソッドでオーバーライドすることで、Derived
クラスをスレッドセーフにしている。
class Base {
public synchronized void doSomething() {
// ...
}
}
class Derived extends Base {
private final Object lock = new Object();
@Override public void doSomething() {
synchronized (lock) {
// ...
}
}
}
この手法が許されるのは、Derived
クラスのロックポリシーが Base
クラスのロックポリシーと整合している場合に限る。
違反コード (private ロック)
以下の違反コード例では、 Base
クラスのなかで、「LCK00-J. 信頼できないコードから使用されるクラスを同期するにはprivate finalロックオブジェクトを使用する」にしたがって、private final 宣言されたロックを使用する doSomething()
メソッドを定義している。
class Base {
private final Object lock = new Object();
public void doSomething() {
synchronized (lock) {
// ...
}
}
}
class Derived extends Base {
Logger logger = // 初期化する
@Override public void doSomething() {
try {
super.doSomething();
} finally {
logger.log(Level.FINE, "Did something");
}
}
}
上記のコードでは、複数のスレッドが実行されている場合、各タスクの実行順序とは異なる順序でログのエントリが出力される可能性がある。Derived
クラスの doSomething()
メソッドはスレッドセーフではないため、複数のスレッドで安全に使用することはできない。
適合コード (private ロック)
以下の適合コードでは、サブクラスの doSomething()
メソッドで独自に private final 宣言されたロックオブジェクトを使用して同期している。
class Base {
private final Object lock = new Object();
public void doSomething() {
synchronized (lock) {
// ...
}
}
}
class Derived extends Base {
Logger logger = // 初期化する
private final Object lock = new Object();
@Override public void doSomething() {
synchronized (lock) {
try {
super.doSomething();
} finally {
logger.log(Level.FINE, "Did something");
}
}
}
}
Base
クラスと Derived
クラスの各オブジェクトは、それぞれ独自のロックを保持しており、互いに相手のクラスのロックにはアクセスできないことに注意。このことにより、Derived
クラスは、Base
クラスとは独立に、スレッドセーフであることを保証している。
リスク評価
スレッドセーフでないメソッドでスレッドセーフなメソッドをオーバーライドすると、予期せぬ振舞いにつながる危険がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
TSM00-J |
低 |
中 |
中 |
P4 |
L3 |
参考文献
[API 2014] |
|
[SDN 2008] |
翻訳元
これは以下のページを翻訳したものです。
TSM00-J. Do not override thread-safe methods with methods that are not thread-safe (revision 107)