根据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只有少数),我很好奇规范说明了这个场景。例如。如果我将字段更新放入synchronized块中将保证其他线程的可见性,或者值将按照规范的最终值缓存在寄存器中。
http://download.oracle.com/otndocs/jcp/memory_model-1.0-pfd-spec-oth-JSpec/
答案 0 :(得分:5)
如果一个对象的状态在构造后不能改变,则该对象被认为是不可变的。 http://docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html
您正在使用该对象作为可变,因为您正在更改其状态。
确实,使用Reflection会破坏教程中定义的不变性,因为您可以使用它来更改非常量的最终字段。
反射抗性不可变对象的示例如下:
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
。但是,嘿,我们总是可以使用本机代码或内存编辑器将该值更改为其他值。
如果你用反射进行黑客攻击,你最好不要将你的领域称为最终,并提供操纵它的方法。
答案 1 :(得分:4)
如果你坚持关闭访问控制并做顽皮的事情,那么所有的赌注都会被反射掉。
静态常量通常在编译时内联,因此改变它们的值可能无论如何都没有影响。结果实际上取决于编译时优化器的巧妙程度,以及JIT编译器在运行时的巧妙程度。
最终结果:“这里有龙,害怕所有敢于踩到这里的人!”
答案 2 :(得分:3)
在这种情况下发生内存一致性错误:
1个线程1使用b1.getField1()读取一个字段并获得1
2个线程2使用b1.setField1(2)
更改字段3现在,当线程1调用b1.getField1()时,它可能再次获得1,因为在没有同步的情况下,JVM可以优化此调用并返回缓存值。
但是如果我们在实例化期间只初始化一个可变bean(可能有反射,就像Spring容器一样),而其他线程只会在初始化后读取它,即使没有任何同步也不会有内存一致性错误
当您使用反射更改字段时,相同的规则适用于不可变对象。