インタビュアー: 同期により命令の並べ替えを禁止できますか? ほとんどの人が勘違いしてしまうでしょう!

命令の並べ替え

1. 問題の説明

まず最初に明確にしておきたいのは、命令の並べ替えと順序付けは同じではないということです。この点は非常に重要です。

私たちはよくこう言います。

  • Volatile はメモリの可視性を保証し、命令の並べ替えを禁止できますが、アトミック性は保証できません。
  • 同期により、原子性、可視性、順序が保証されます。

注: ここでの順序は、命令の再順序付けを禁止できることを意味するものではありません。

例えば:

ダブルチェックシングルトンモードでは、synchronized が追加されているのに、なぜ変数を変更するために volatile が必要なのでしょうか? synchronized で命令の再配置を禁止できるのであれば、volatile はまったく必要ありません。

オープンソースで無料の Spring Boot 実践プロジェクトを推奨します。

https://github.com/javastacks/spring-boot-best-practice

2. DCLコードのバイトコード解析命令の並び替え問題

最初に知っておく必要がある知識ポイント: Object obj = new Object(); このコードはアトミックな操作ではなく、3 つのステップに分かれています。

  • メモリ内のスペースを申請し、新しいオブジェクトを作成します。
  • new からこのオブジェクトのコンストラクターを呼び出します。
  • このオブジェクトへの参照を obj に割り当てます。
a)、DCL ダブルチェックコード
public class MySingleton {

    private static MySingleton INSTANCE;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (MySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new MySingleton();
                }
            }
        }
        return INSTANCE;
    }
}
b)、バイトコードは次のとおりです。

バイトコードからわかるように、new MySingleton();このコードはバイトコード 17、20、21、および 24 の 4 行に対応します (20 行は参照のコピーであり、無視できます)。

  • まず、17 行目でメモリ内のスペースを開いて、MySingleton オブジェクトを作成します。
  • 次に、21 行目でオブジェクトのコンストラクターを呼び出します。
  • 次に、24 行目でオブジェクトの参照を静的変数 INSTANCE に割り当てます。

上記は私たちが期待する実行順序であり、各スレッドがこの順序で命令を実行することを望んでいます (これは命令の並べ替えを禁止するためです)。ただし、操作効率を向上させるために、コンピューターは命令の順序を最適化します (たとえば、上記の順序は、17、24、21 のように最適化され、再配置される場合があります)。

命令の並べ替えに関する問題

  • 効率を向上させるために、コンピューターはコードの順序を最適化します。たとえば、t1 スレッドの実行順序は 17、24、21 であり、t2 スレッドの実行順序は 17、21、24 です (つまり、シングルスレッドの場合はどのような順序で実行しても問題ありません)。
  • t1 スレッドがロックを取得してオブジェクト作成を実行すると、最初に 24 行目を実行し、オブジェクトの参照を静的変数 INSTANCE に代入します (この時点では、オブジェクトはまだコンストラクターを呼び出しておらず、オブジェクトはまだ呼び出されていません)。完全なオブジェクト)。
  • このとき、t2 スレッドが実行されます。t2 スレッドがif (INSTANCE == null)(コード 16 行目) ステートメントを実行すると、INSTANCE が空ではないことがわかり、t2 スレッドは INSTANCE オブジェクトを直接返します。ただし、この時点ではオブジェクトはまだ不完全なオブジェクトであるため、t2 スレッドがそのオブジェクトを使用するときに問題が発生します。

したがって、単一スレッドでの命令の並べ替えには問題はありませんが、マルチスレッドが関与すると、命令の並べ替えによって予期しない結果が生じる可能性があります。

秩序

それでは、synchronized は命令の並べ替えを禁止できないため、保証される順序はどのようなものになるのでしょうか?

その本質は、同期された変更されたメソッドを呼び出すときに、複数のスレッドが並列 (同時) 呼び出しから直列呼び出しに変更できるようにすることであり、ロックを取得した人が実行することになります。

1. コード例

スレッド t1 と t2 は両方ともシングルトン オブジェクトを取得してからテスト メソッドを呼び出す必要があります。テスト メソッドは同期ロックのあるメソッドです。

public class MySingleton {

    private static MySingleton INSTANCE;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (MySingleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new MySingleton();
                }
            }
        }
        return INSTANCE;
    }

    public static void test(final MySingleton singleton) {
        synchronized (MySingleton.class) {
            System.out.println(singleton);
        }
    }
}

テストコード

public class MySingletonTest {
  // 可以看到两个线程都需要去获取单例对象,然后调用test方法,并且test方法是加了同步锁的方法
    public static void main(final String[] args) {
        new Thread(() -> {
            MySingleton instance = MySingleton.getInstance();
            MySingleton.test(instance);
        }, "t1").start();
        new Thread(() -> {
            MySingleton instance = MySingleton.getInstance();
            MySingleton.test(instance);
        }, "t2").start();
    }
}

t2 スレッドがコンストラクタを呼び出していないオブジェクトを取得した場合でも、MySingleton.test(instance);t2 スレッドでメソッドを呼び出す際には問題ありません。これは、同期ロックが使用されており、各メソッドが 1 つのロックで実行されるためです。 t2 スレッドがロックを取得して実行するとき、t1 はすでにロックを解放しており、この時点でインスタンスはすでにインスタンス化されています。それで問題ありません。

したがって、同期保証は、同時実行をシリアルに変換することを指しますが、内部命令の並べ替えは保証されません。

出典: blog.csdn.net/Hellowenpan/article/details/117750543

最近のおすすめ記事:

1. 1,000 を超える Java 面接の質問と回答 (2022 年最新バージョン)

2.素晴らしい!Java コルーチンが登場します。

3. Spring Boot 2.x チュートリアル、包括的すぎる!

4.画面を爆発や爆発で埋め尽くさないで、デコレーターモードを試してください。これがエレガントな方法です。

5.最新リリースの「Java 開発マニュアル (松山編)」をすぐにダウンロードしてください!

気分がいいので、「いいね!」+「転送」を忘れないでください!

おすすめ

転載: blog.csdn.net/youanyyou/article/details/132563045