发布对像
定义:是一个对象能够被当前范围之外的代码所使用
对象溢出
一种错误的发布。当一个对象该没有构造完成时,就使被其他线程所见。
下面我们来看一下没有安全发布的对象
@Slf4j
public class UnsafePublish {private String[] states = {"a", "b", "c"};
public String[] getStates() {return states;}
public static void main(String[] args) {UnsafePublish unsafePublish = new UnsafePublish();
log.info("{}", Arrays.toString(unsafePublish.getStates()));
unsafePublish.getStates()[0] = "d";
log.info("{}", Arrays.toString(unsafePublish.getStates()));
}
}
我们看这段代码,我们创建了一个对象通过 getStates 方法我们可以获取这个对象的数组,此时我们将数组内容打印出来结果, 如果此时我们将这个对象发布出去,然后其他线程(这里没有模拟其他线程对其修改)又对这个对象的 states 的值进行修改,此时在拿到这个对象的期望的是没有被修改的,事实上得到的对象是修改过后的。也就是说我们不能直接通过一个 public 的一个 set 方法就行 return。
下面我们再看一段对象溢出的代码
public class ThisEscape {public ThisEscape(EventSource source) {source.registerListener(new EventListener() {public void onEvent(Event e) {doSomething(e);
}
});
}
void doSomething(Event e) { }
interface EventSource {void registerListener(EventListener e);
}
interface EventListener {void onEvent(Event e);
}
interface Event {}}
这将导致 this 逸出,所谓逸出,就是在不该发布的时候发布了一个引用。在这个例子里面,当我们实例化 ThisEscape 对象时,会调用 source 的 registerListener 方法,这时便启动了一个线程,而且这个线程持有了 ThisEscape 对象(调用了对象的 doSomething 方法),但此时 ThisEscape 对象却没有实例化完成(还没有返回一个引用),所以我们说,此时造成了一个 this 引用逸出,即还没有完成的实例化 ThisEscape 对象的动作,却已经暴露了对象的引用。其他线程访问还没有构造好的对象,可能会造成意料不到的问题。
public class SafeListener {
private final EventListener listener;
private SafeListener() {listener = new EventListener() {public void onEvent(Event e) {doSomething(e);
}
};
}
public static SafeListener newInstance(EventSource source) {SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
void doSomething(Event e) { }
interface EventSource {void registerListener(EventListener e);
}
interface EventListener {void onEvent(Event e);
}
interface Event {}}
在这个例子中我们使用匿名类的形式来构造,只有在整个对象都实例化好了才能会执行。只有当构造函数返回时,this 引用才应该从线程中逸出。构造函数可以将 this 引用保存到某个地方,只要其他线程不会在构造函数完成之前使用它