static 宣言した共有データの保護に、インスタンスロックを用いてはならない。なぜなら、インスタンスロックはそのクラスのインスタンスが複数生成された場合にデータを保護できないからである。すなわち、static 宣言したロックオブジェクトを使用しない限り、共有オブジェクトへの並行アクセスは保護されない。クラスが信頼できないコードとやり取りする場合には、「LCK00-J. 信頼できないコードから使用されるクラスを同期するにはprivate finalロックオブジェクトを使用する」に従い、ロックを private final 宣言しなければならない。
違反コード (static宣言したデータに対するstatic宣言していないロックオブジェクト)
以下の違反コード例では、static 宣言していないロックオブジェクトを使用して、static 宣言した counter フィールドへのアクセスを保護しようとしている。Runnable タスクを2つ開始すると、ロックオブジェクトのインスタンスが2つ生成され、それぞれのタスクが別々のインスタンスロックを使うことになる。
public final class CountBoxes implements Runnable { private static volatile int counter; // ... private final Object lock = new Object(); @Override public void run() { synchronized (lock) { counter++; // ... } } public static void main(String[] args) { for (int i = 0; i < 2; i++) { new Thread(new CountBoxes()).start(); } } }
このコード例では、適切な同期を行っていないので、volatile フィールドのインクリメント操作はアトミックではなく、各スレッドが参照する counter 値は不整合となる(「VNA02-J. 共有変数への複合操作のアトミック性を確保する」を参照)。
違反コード (staticデータに対するメソッド同期)
以下の違反コード例では、static 宣言した counter フィールドへのアクセスを保護するためにメソッドを同期させている。
public final class CountBoxes implements Runnable { private static volatile int counter; // ... public synchronized void run() { counter++; // ... } // ... }
この場合、メソッド同期でクラス自体の固有ロックではなくクラスの各インスタンスの固有ロックを使っている。したがって、異なる Runnable インスタンスを使って構築されたスレッドが参照する counter の値は、整合性を欠いたものになってしまうかもしれない。
適合コード (staticロックオブジェクト)
以下の適合コードでは、static 宣言したロックオブジェクトを使うことにより、インクリメント操作のアトミック性を確保している。
public class CountBoxes implements Runnable { private static int counter; // ... private static final Object lock = new Object(); public void run() { synchronized (lock) { counter++; // ... } } // ... }
同期を行うのであれば、counter 変数を volatile 宣言する必要はない。
リスク評価
static 宣言した共有データの保護にインスタンスロックを使うと、予期せぬ結果につながりうる。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
LCK06-J | 中 | 中 | 中 | P8 | L2 |
自動検出
いくつかの静的解析ツールは、このルールの違反を検出できる。
関連ガイドライン
MITRE CWE | CWE-667. Improper Locking |
参考文献
[API 2006] |
翻訳元
これは以下のページを翻訳したものです。
LCK06-J. Do not use an instance lock to protect shared static data (revision 58)