Java
 Computer >> コンピューター >  >> プログラミング >> Java

Javaのオブジェクトレベルロックとクラスレベルロックの違いを徹底解説

マルチスレッドアプリケーションにおいて、オブジェクトレベルロッククラスレベルロックは、どちらも同期(synchronization)を実現するための重要な仕組みです。この記事では、それぞれの特徴や動作の違いを、コード例と実行結果とともにわかりやすく解説します。

オブジェクトレベルロックとは

Javaでは、すべてのオブジェクトが固有のロックを1つ持っています。あるスレッドが特定のオブジェクトに対してsynchronizedメソッドを実行したい場合、まずそのオブジェクトのロックを取得する必要があります。

ロックを取得できたスレッドは、そのオブジェクトの任意のsynchronizedメソッドを実行できます。そして、メソッドの実行が完了すると、スレッドは自動的にロックを解放します。なお、ロックの取得と解放はJVMが内部的に処理してくれるため、開発者が明示的に管理する必要はありません。

オブジェクトレベルロックは、非staticメソッド非staticコードブロックを同期させたい場合に使われる仕組みです。これにより、クラスの特定のインスタンスに対して、同時に1つのスレッドしかコードブロックを実行できないようになります。インスタンスレベルのデータをスレッドセーフにしたい場合に必ず役立ちます。

オブジェクトレベルロックのコード例

public class ObjectLevelLockTest implements Runnable {
    @Override
    public void run() {
        objectLock();
    }
    public void objectLock() {
        System.out.println(Thread.currentThread().getName());
        synchronized(this) {
            System.out.println("Synchronized block " + Thread.currentThread().getName());
            System.out.println("Synchronized block " + Thread.currentThread().getName() + " end");
        }
    }
    public static void main(String[] args) {
        ObjectLevelLockTest test1 = new ObjectLevelLockTest();
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test1);
        ObjectLevelLockTest test2 = new ObjectLevelLockTest();
        Thread t3 = new Thread(test2);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

実行結果

t1
t2
t3
Synchronized block t1
Synchronized block t3
Synchronized block t1 end
Synchronized block t3 end
Synchronized block t2
Synchronized block t2 end

この出力から注目すべき点は、t1とt3のsynchronizedブロックが交互に実行されていることです。これは、t1とt2が同じインスタンス(test1)のロックを競い合う一方、t3は別のインスタンス(test2)のロックを使用しているためです。つまり、異なるインスタンスのロックは互いに独立しており、並行して実行できることがわかります。逆に、同じインスタンスを共有するt1とt2のブロックが重なって実行されることはありません。

クラスレベルロックとは

Javaでは、すべてのクラスが固有のロックを1つ持っており、これがクラスレベルロックです。スレッドがstatic synchronizedメソッドを実行したい場合には、このクラスレベルロックが必要になります。

クラスレベルロックを取得したスレッドは、そのクラスの任意のstatic synchronizedメソッドを実行できます。メソッドの実行が完了すると、スレッドは自動的にロックを解放します。

クラスレベルロックの最大の特徴は、実行時に存在するすべてのインスタンスを対象として、複数のスレッドがsynchronizedブロックに同時に入るのを防げる点です。つまり、インスタンスごとではなく、クラス全体で1つのスレッドだけが実行できるように制御できます。

クラスレベルロックのコード例

public class ClassLevelLockTest implements Runnable {
    @Override
    public void run() {
        classLock();
    }
    public void classLock() {
        System.out.println(Thread.currentThread().getName());
        synchronized(ClassLevelLockTest.class) {
            System.out.println("Synchronized block " + Thread.currentThread().getName());
            System.out.println("Synchronized block " + Thread.currentThread().getName() + " end");
        }
    }
    public static void main(String[] args) {
        ClassLevelLockTest test1 = new ClassLevelLockTest();
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test1);
        ClassLevelLockTest test2 = new ClassLevelLockTest();
        Thread t3 = new Thread(test2);
        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

実行結果

t3
t2
t1
Synchronized block t3
Synchronized block t3 end
Synchronized block t2
Synchronized block t2 end
Synchronized block t1
Synchronized block t1 end

オブジェクトレベルロックの場合とは対照的に、今回は各スレッドのsynchronizedブロックが完全に直列化されています。t3は別インスタンス(test2)を使っていますが、ロックの対象がClassLevelLockTest.classというクラスそのものであるため、どのインスタンスから呼び出されても同じロックを競い合うことになるのです。その結果、「開始〜終了」がスレッドごとに順番に実行されます。

オブジェクトレベルロックとクラスレベルロックの違いまとめ

項目オブジェクトレベルロッククラスレベルロック
ロックの対象クラスの各インスタンス(this)クラスそのもの(ClassName.class)
適用対象非staticメソッド・非staticブロックstatic synchronizedメソッド・静的コンテキスト
排他範囲同一インスタンス内でのみ排他全インスタンスを通じて排他
主な用途インスタンスデータのスレッドセーフ化staticデータや共有リソースの保護

このように、インスタンス単位で排他制御したいのか、クラス全体で排他制御したいのかによって、適切なロックの使い分けが求められます。マルチスレッド環境で安全なプログラムを書くために、それぞれの特性を正しく理解しておきましょう。

  1. JavaのSwingWorkerクラスとは?GUIアプリで非同期処理を行う重要性と使い方を解説

    SwingWorkerクラスは、時間のかかる処理(長時間実行タスク)などをワーカースレッド上で非同期に実行し、その結果に基づいてイベントディスパッチスレッド(EDT)からSwingコンポーネントを安全に更新するための仕組みを提供します。このクラスはJava 1.6で導入されました。Swingでは、すべてのUI更新はEDT上で行う必要があります。しかし、EDT上で重い処理を直接実行すると画面がフリーズしてしまい、ユーザー体験を大きく損ないます。SwingWorkerを使えば、バックグラウンドでの処理とUIの更新を適切に分離できるため、応答性の高いデスクトップアプリケーションを実現できます。Swi

  2. JavaのSwingUtilitiesクラスとは?invokeAndWait()とinvokeLater()で実現するスレッドセーフなGUI操作

    JavaのSwingでは、コンポーネントが画面に表示された後、それらを操作できるのは「イベントディスパッチスレッド(Event Handling Thread)」と呼ばれる単一のスレッドだけです。これはSwingの重要な設計原則であり、複数のスレッドから同時にGUIを操作すると不整合や予期しない動作が発生するため、すべての描画・更新処理をこのスレッドに集約することで安全性を確保しています。開発者は、別途作成したコードブロックの参照をイベントディスパッチスレッドに渡すことができます。SwingUtilitiesクラスには、そのための2つの重要なstaticメソッド、invokeAndWait()