14

根据JSR-133 ,不可变对象是线程安全的,不需要同步。但是,可以使用反射更新最终字段的值:

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 只是少数),我很好奇规范对这种情况的看法。例如,如果我将字段更新放入同步块中,将保证在其他线程中的可见性,或者值将根据最终规范缓存在寄存器中。

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(可能像 Spring 容器那样使用反射)并且其他线程只会在初始化后读取它,即使没有任何同步也不会出现内存一致性错误

当您通过反射更改其字段时,相同的规则适用于不可变对象。

于 2013-04-05T11:25:13.240 回答