java并发编程实践书中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了。这是危及到线程安全的,因为其他线程有可能通过这个逸出的引用访问到“初始化了一半”的对象(partially-constructed object)。这样就会出现某些线程中看到该对象的状态是没初始化完的状态,而在另外一些线程看到的却是已经初始化完的状态,这种不一致性是不确定的,程序也会因此而产生一些无法预知的并发错误。在说明并发编程中如何避免this引用逸出之前,我们先看看一个对象是如何产生this引用逸出的。
直接贴出一个简单消费者生产者的场景
具体业务类
public class EventSource<T> {
private final List<T> eventListeners ;
public EventSource() {
eventListeners = new ArrayList<T>() ;
}
//注册事件然后启动消费者线程消费
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<T>(eventListeners.size());
dest.addAll(eventListeners);
return dest;
}
}
public class ThisEscape {
public final int id;
public final String name;
public ThisEscape(EventSource<EventListener> source) {
id = 1;
source.registerListener(new EventListener() {
public void onEvent(Object obj) {
System.out.println("id: "+ThisEscape.this.id);
System.out.println("name: "+ThisEscape.this.name);
}
});
name = "flysqrlboy";
}
}
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());
}
}
}
测试类
public class ThisEscapeTest {
public static void main(String[] args) {
EventSource<EventListener> source = new EventSource<EventListener>();
ListenerRunnable listRun = new ListenerRunnable(source);
Thread thread = new Thread(listRun);
thread.start();
ThisEscape escape1 = new ThisEscape(source);
}
}
- 首先启动一个消费者线程,因为没有事件可以消费所有wait
- ThisEscape escape1 = new ThisEscape(source);会调用
public ThisEscape(EventSource<EventListener> source) {
id = 1;
source.registerListener(new EventListener() {
public void onEvent(Object obj) {
System.out.println("id: "+ThisEscape.this.id);
System.out.println("name: "+ThisEscape.this.name);
}
});
name = "flysqrlboy";
}
- 但source.registerListener(XX)会调用notifyAll(),启动了所有正在等待的消费者,开始抢EVENT消费,正巧被消费者抢到了(发生了上下文切换)。
name = "flysqrlboy";
没有机会执行
结果:消费者线程读到了
这样的数据
ThisEscape{
id=1;
name=null;
}
这就造成了线程不安全
解决方案
在消费之前必须构造好对象
这里使用工厂的方式优雅的解决了这个问题
public class ThisSafe {
public final int id;
public final String name;
private final EventListener listener;
private ThisSafe() {
id = 1;
listener = new EventListener(){
public void onEvent(Object obj) {
System.out.println("id: "+ThisSafe.this.id);
System.out.println("name: "+ThisSafe.this.name);
}
};
name = "flysqrlboy";
}
public static ThisSafe getInstance(EventSource<EventListener> source) {
ThisSafe safe = new ThisSafe();
source.registerListener(safe.listener);
return safe;
}
}
结论
因不完全构造完对象而直接消费对象造成线程不安全问题,尤其是在构造方法开线程的时候要注意!!