创建线程的第四种方式
Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
题外话:
并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了。这是危及到线程安全的,因为其他线程有可能通过这个逸出的引用访问到“初始化了一半”的对象(partially-constructed object)。这样就会出现某些线程中看到该对象的状态是没初始化完的状态,而在另外一些线程看到的却是已经初始化完的状态,这种不一致性是不确定的,程序也会因此而产生一些无法预知的并发错误。在说明并发编程中如何避免this引用逸出之前,我们先看看一个对象是如何产生this引用逸出的。
如代码清单1所示,ThisEscape在构造函数中引入了一个内部类EventListener,而内部类会自动的持有其外部类(这里是ThisEscape)的this引用。source.registerListener会将内部类发布出去,从而ThisEscape.this引用也随着内部类被发布了出去。但此时ThisEscape对象还没有构造完成 —— id已被赋值为1,但name还没被赋值,仍然为null。
This引用逃逸是指构造函数并没有完全完成,但内部的属性已发布出去,并未按正确的意愿初始化。则产生了多线程并发访问的问题。
导致的this引用逸出是怎样产生的。它需要满足两个条件:
- 在构造函数中创建内部类(EventListener)
- 在构造函数中就把这个内部类给发布了出去(source.registerListener)
因此,我们要防止这一类this引用逸出的方法就是避免让这两个条件同时出现。也就是说,如果要在构造函数中创建内部类,那么就不能在构造函数中把他发布了,应该在构造函数外发布,即等构造函数执行完毕,初始化工作已全部完成,再发布内部类。可以使用一个私有的构造函数进行初始化和一个公共的工厂方法进行发布。
另一种导致this引用逸出的常见错误,是在构造函数中启动一个线程。其原理跟上文说的内部类导致的this引用逸出相类似。解决的办法也相似,即可以在构造函数中创建线程,但别启动它。在构造函数外面再启动。
为什么提倡在线程池中启动线程,而非直接使用new Thread().start() ?
- 更易管理
- 效率更高 (池化技术,节约开销)
- 有助于避免this引用逃逸
这个有助于避免this引用的逃逸是因为要启动一个线程,可能会有实现runnable的方式,那么实现此方式如果启动线程的话,是需要按构造参数传入thread的。因此可能会把内部的类发布出去,导致this引用逃逸。但是如果仅是将任务提交给线程池,那么任务的执行便会被封装调用。