14

JSR-133によると、不変オブジェクトはスレッド セーフであり、同期は必要ありません。ただし、リフレクションを使用して final フィールドの値を更新することは可能です:

package com.stackoverflow;

import java.lang.reflect.Field;

public class WhatsGoingOn {

    static class Immutable {
        private final int value;

        public Immutable(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        final Immutable immutable = new Immutable(Integer.MIN_VALUE);

        final Field f = Immutable.class.getDeclaredField("value");
        f.setAccessible(true);

        System.out.println(immutable.getValue());
        f.set(immutable, Integer.MAX_VALUE);
        System.out.println(immutable.getValue());
    }
}

リフレクションに依存するフレームワークの数 (Spring と Hibernate はほんの一部) を考えると、このシナリオについて仕様が何を示しているのか興味があります。たとえば、フィールドの更新を同期ブロックに入れると、他のスレッドでの可視性が保証されるか、値が final の仕様に従ってレジスタにキャッシュされます。

http://download.oracle.com/otndocs/jcp/memory_model-1.0-pfd-spec-oth-JSpec/

4

3 に答える 3

6

構築後に状態を変更できないオブジェクトは、不変と見なされます。 http://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html

状態を変更しているため、そのオブジェクトを可変として使用しています。

リフレクションを使用すると、チュートリアルで定義されているように不変性が壊れることは事実です。リフレクションを使用して非定数の最終フィールドを変更できるからです。

リフレクション耐性の不変オブジェクトの例は次のとおりです。

static class Immutable {
    // This field is a constant, and cannot be changed using Reflection
    private final int value = Integer.MIN_VALUE;

    public int getValue() {
        return value;
    }
}

public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
    final Immutable immutable = new Immutable();

    final Field f = Immutable.class.getDeclaredField("value");
    f.setAccessible(true);

    System.out.println(immutable.getValue());
    f.set(immutable, Integer.MAX_VALUE);
    System.out.println(immutable.getValue());
}

この場合、リフレクション テストは失敗し、値はそのままになりますInteger.MIN_VALUE。しかし、いつでもネイティブ コードまたはメモリ エディターを使用して、その値を別の値に変更できます。

ここまでリフレクションを使ったハッキン​​グを行う場合は、フィールドを final と呼んだり、それを操作するメソッドを提供したりしない方がよいでしょう。

于 2013-04-05T11:52:24.570 に答える
5

アクセス制御をオフにしていたずらをすることを主張する場合、すべての賭けは反映されません。

通常、静的定数はコンパイル時にインライン化されるため、値を変更してもおそらく影響はありません。結果は、オプティマイザーがコンパイル時にどれだけ賢いか、および JIT コンパイラーが実行時にどれだけ賢いかによって大きく異なります。

最終結果: 「ここにドラゴンがいます。ここを踏みにじるすべての人を恐れてください!」

于 2013-04-05T10:41:51.757 に答える
3

このシナリオでは、メモリ整合性エラーが発生します。

1 スレッド 1 は b1.getField1() でフィールドを読み取り、1 を取得します

2 スレッド 2 は b1.setField1(2) でフィールドを変更します

3 現在、スレッド 1 が b1.getField1() を呼び出すと、再び 1 を取得する可能性があります。これは、同期がない場合、JVM がこの呼び出しを最適化し、キャッシュされた値を返すことが許可されているためです。

しかし、可変 Bean をインスタンス化中に 1 回だけ初期化し (Spring コンテナーのようにリフレクションを使用する可能性があります)、他のスレッドが初期化後にそれを読み取るだけの場合、同期化がなくてもメモリ一貫性エラーは発生しません。

リフレクションでフィールドを変更する場合、同じ規則が不変オブジェクトに適用されます。

于 2013-04-05T11:25:13.240 に答える