常见的线程池是fixed thread pool,即线程池中可运行的最大线程数是固定的。它使用一个内部queue来排队,当线程池中运行的线程达到上限时,其他的任务需要等待。当一个线程执行的任务(runnable)结束时,线程继续执行下一个runnable,这种方式可节约大量创建thread所消耗的资源。
继续使用上文中的例子说明如何使用executor和线程池:
//这里注意scanner和transfer必须共享同一个对象fileArray。
//我们希望有一个线程来扫描目录,有多个线程来传输扫描到的文件
//首先实例化这两个类:扫描类和传输类。我们将在后面的代码中使用这两个实例来创建线程。
FTPFileTransfer fileTransfer = new FTPFileTransfer( fileArray);
FolderScanner folderScanner = new FolderScanner( fileArray);
private void run() {
//首先取得在配置文件中配置的希望在线程池中使用多少个线程数
int threadPoolNumber = Integer.parseInt(getAppProperty("ThreadPoolNumber"));
//使用类Executors的静态方法返回线程池。
//Executors返回线程池的静态方法有很多,返回不同种类的线程池。不同种类的线程池实现的也是不同的executor接口。
//executor有三种接口:Executor, ExecutorService 和 ScheduledExecutorService。
ExecutorService pool = Executors.newFixedThreadPool(threadPoolNumber);
//用submit放入pool中一个task。
//这样会立即create一个线程用于运行folderScanner类中的run方法。
//注意submit的是一个实现了runnable或callable或future的对象,
//而不是 一个thread。这样是不对的:pool.submit(new Thread(folderScanner));
//不使用submit提交task,则不会有线程被创建。
//下面就创建了一个在线程池中的线程,该线程执行的是扫描目录的功能。
pool.submit(folderScanner);
//对于要使用多个线程的tranfer,则需要使用for循环submit足够多的task。因为有一个线程已经被用作scanner了,所以这里threadPoolNumber要减1.
for(int i = 0; i < threadPoolNumber-1; i++){
pool.submit(fileTransfer);
}
//这段code的作用仅仅是让main线程一直运行不退出。
while(!getIfStop()){
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//不要忘记使用完后要在主线程中shutdown线程池。
//shutdown会等待线程都执行完毕后结束,也有方法可以实现立即结束。
//如果不是使用线程池,而是直接创建的线程,则可以使用join等待子线程结束。
pool.shutdown();
}
注意上面代码中的 pool.submit(folderScanner); 也就是说向线程池中提交的不是一个线程,而是一个对象。提交对象后,线程池会立即create 一个thread来运行folderScanner。当folderScanner运行结束后,该thread被复用,运行其他对象的run方法。因此线程池中的线程并不会和具体的runnable绑定在一起,更像是container。
因为这个小程序是跑在window上的一个service,所以使用stopHander作为service停止的相应动作。各个子线程都应定时检查静态成员变量ifstop标志,发现后就应设法停止,从而令主线程pool.shutdown(); 正常停止。
public static void stopHandler(String[] args) {
ifStop = true;
}
public static boolean getIfStop(){
return ifStop;
}