发布
发布(publish)一个对象的意思是指,使对象能够在当前作用域之外的代码中使用。
可以通过公有静态变量,非私有方法,构造方法内隐含引用 三种方式。
public static Set<String> knownSecrets;
public void initialize(){
knownSecrets = new HashSet<>();
}
逸出
当某个不应该发布的对象被发布时,这种情况就被称为逸出(Escape)。
内部可变状态逸出
public class UnsafeStates {
private String[] states = new String[]{"AK", "AL", "AJ"};
public String[] getStates() {
return states;
}
@Override
public String toString() {
return states[0] + "," + states[1] + "," + states[2];
}
}
public class UnsafeStatesTest {
public static void main(String[] args) {
UnsafeStates us = new UnsafeStates();
System.out.println(us);
String[] str = us.getStates();
str[0] = "yangyun";
System.out.println(us);
}
}
output:
AK,AL,AJ
yangyun,AL,AJ
由输出结果得出,任何调用者都能修改这个数组的内容。在这个示例中,数组states已经逸出了它所在的作用域,因为这个本应是私有的变量已经被发布了。
当发布一个对象时,在该对象的非私有域中引用的所有对象同样会被发布。一般来说,如果一个已经发布的对象能够通过非私有的变量引用和方法调用到达其他对象,那么这些对象也都会被发布。
所以我们需要封装,封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏设计约束条件变得更难。
this逸出
内部类导致的this引用逸出示例:
EventListener
public interface EventListener {
void onEvent(Object obj);
}
EventSource
import java.util.ArrayList;
import java.util.List;
public class EventSource<T> {
private final List<T> eventListeners;
public EventSource() {
eventListeners = new ArrayList<>();
}
public synchronized void registerListener(T eventListener) {
this.eventListeners.add(eventListener);
this.notifyAll();
}
public synchronized List<T> retrieveListeners() throws InterruptedException {
List<T> dest = null;
if (eventListeners.size() <= 0) {
this.wait();
}
dest = new ArrayList<>(eventListeners.size());
dest.addAll(eventListeners);
return dest;
}
}
ThisEscape
public class ThisEscape {
public final int id;
public final String name;
public ThisEscape(EventSource<EventListener> source) {
id = 1;
source.registerListener(new EventListener() {
@Override
public void onEvent(Object obj) {
System.out.println("id: " + ThisEscape.this.id);
System.out.println("name: " + ThisEscape.this.name);
}
});
try {
// 调用sleep模拟其他耗时的初始化操作
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
name = "flysqrlboy";
}
}
ListenerRunnable
import java.util.List;
public class ListenerRunnable implements Runnable {
private EventSource<EventListener> source;
public ListenerRunnable(EventSource<EventListener> source) {
this.source = source;
}
@Override
public void run() {
List<EventListener> listeners = null;
try {
listeners = this.source.retrieveListeners();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(EventListener listener : listeners) {
listener.onEvent(new Object());
}
}
}
ThisEscapeTest
public class ThisEscapeTest {
public static void main(String[] args) {
EventSource<EventListener> source = new EventSource<>();
ListenerRunnable listRun = new ListenerRunnable(source);
Thread thread = new Thread(listRun);
thread.start();
ThisEscape escape1 = new ThisEscape(source);
System.out.println("ThisEscape 构造完成结果:"+escape1);
}
}
output:
id: 1
name: null
ThisEscape 构造完成结果:id[1],name[yy]
产生逸出原因
- 一个是在构造函数中创建内部类(EventListener)
- 一个是在构造函数中就把这个内部类给发布了出去(source.registerListener)。
解决方法
如果要在构造函数中创建内部类,那么就不能在构造函数中将其发布了,应该在构造函数外发布,即等构造函数执行完毕,初始化工作已全部完成,再发布内部类。
当且仅当对象的构造函数返回时,对象才处于可预测的和一致的状态。
public class ThisSafe {
public final int id;
public final String name;
private final EventListener listener;
private ThisSafe() {
id = 1;
listener = new EventListener() {
@Override
public void onEvent(Object obj) {
System.out.println("id: " + ThisSafe.this.id);
System.out.println("name: " + ThisSafe.this.name);
}
};
name = "yy";
}
public static ThisSafe getInstance(EventSource<EventListener> source) {
ThisSafe safe = new ThisSafe();
source.registerListener(safe.listener);
return safe;
}
@Override
public String toString() {
return "id[" + id + "],name[" + name + "]";
}
}
public class ThisEscapeTest {
public static void main(String[] args) {
EventSource<EventListener> source = new EventSource<>();
ListenerRunnable listRun = new ListenerRunnable(source);
Thread thread = new Thread(listRun);
thread.start();
ThisSafe thisSafe = ThisSafe.getInstance(source);
System.out.println(thisSafe);
}
}
output:
id[1],name[yy]
id: 1
name: yy
在构造函数过程中使this引用逸出的一个常见错误是,在构造器中启动一个线程。当对象在其构造函数中创建一个线程时,无论是显式创建还是隐式创建,this引用都会被新创建的线程共享。在对象尚未完全构造之前,新的线程就可以看见它。在构造函数中创建线程并没有错误,但最好不要立即启动它,而是通过一个start或initialize方法来启动。
在函数中调用一个可改写的实例方法时(既不是私有方法,也不是终结方法),同样会导致this引用在构造过程逸出。