java线程池学习(四) —— Executors类

上一章我们介绍了ExecutorService接口,以及它的实现类ThreadPoolExecutor

那么这里我们将介绍Executors类,它可以更进一步的简化我们的工作,直接创建一些预定义过的线程池

这个类也在java.util.concurrent包下。它有如下的几个比较常用的创建线程池的方法:

一:newFixedThreadPool

创建一个线程可重用的并且线程数固定的线程池。

nThreads - 池中的线程数

threadFactory - 创建新线程时使用的工厂

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newFixedThreadPool(int nThreads,ThreadFactory threadFactory)

二:newCachedThreadPool

创建一个可根据实际情况动态维持线程数的线程池,当任务到来时,如果有已经构造好的空闲线程将重用它们,不创建新的线程。

如果没有可用的空闲线程,则创建一个新线程并添加到池中。并且会终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因

此,长时间保持空闲的线程池不会使用任何资源。

threadFactory - 创建新线程时使用的工厂

public static ExecutorService newCachedThreadPool()
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

三:newSingleThreadExecutor

创建一个使用单个线程的 ExecutorService,以无界队列方式来运行该线程。

threadFactory - 创建新线程时使用的工厂

public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

关于这3类的线程池我引用一下《thinking in java》对它们的描述:

使用FixedThreadPool,你可以一次性的预先执行代价高昂的线程分配,因而也就可以限制线程数的数量,这可以节省时间,因为你不用为每一个任务都固定的付出创建线程的开销。在事件驱动的系统中,需要线程的事件处理器,通过直接从池中获取线程,也可以如你所愿地尽快得到服务。你不会滥用可获得的资源,因为FixedThreadPool使用的Thread对象的数量是有界的。

对于CachedThreadPool,它在程序的执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的ExecutorService的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool。

SingleThreadExecutor就是线程数量为1的FixedThreadPool。这对于你希望在另一个线程中连续运行的任何事物(长期存活的任务)来说,都是非常有用的,例如监听进入套接字连接的任务。

=======================================================================

我们再来看一下它们的源码,比如FixedThreadPool:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

所以在本质上,就是创建了一个我们上一篇文章介绍过的ThreadPoolExecutor类。

=======================================================================

接下来我们写一个完整的,使用它们的例子:

下面给出了一个网络服务的简单结构,这里线程池中的线程作为传入的请求。它使用了预先配置的 Executors.newFixedThreadPool(int) 方法创建线程池:

class NetworkService implements Runnable {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;

    public NetworkService(int port, int poolSize)
        throws IOException {
      serverSocket = new ServerSocket(port);
      pool = Executors.newFixedThreadPool(poolSize);
    }
 
    public void run() { // run the service
      try {
        for (;;) {
          pool.execute(new Handler(serverSocket.accept()));
        }
      } catch (IOException ex) {
        pool.shutdown();
      }
    }
}

class Handler implements Runnable {
    private final Socket socket;
    Handler(Socket socket) { this.socket = socket; }
    public void run() {
      // read and service request on socket
    }
}

============================

可以返回结果的“Runnable”

我们知道ExecutorService框架使用Runnble作为其基本的任务表示形式。但是它有很大的局限性就是它不能返回一个值,或者抛出一个受检查的异常

实际上许多需要知道执行结果的任务都需要一定的执行时间的,比如执行数据库的查询,或者从网络上获取一些资源,更或者进行一些比较复杂的计算。对于这些任务Callable是一种更好的抽象。你可以把它当成有返回值的“Runnable”,它的call()方法就相当于Runnable的run()方法。但关键是它的call()方法将返回一个值,并可能抛出一个异常。

那么怎么在ExecutorService框架中很好的使用Callable呢。这里就需要使用到Feture接口。

ExecutorService中的所有submit方法都将返回一个Future对象,从而可以将Callable提交给ExecutorService,并得到一个Future用来获得任务的执行结果或者取消任务。

public class Test {
	private final ExecutorService executor = Executors.newFixedThreadPool(3);
	public void runTheTask(){
		Future<String> future = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result";
			}
			
		});
		try {
			System.out.println( future.get());
		} catch (InterruptedException | ExecutionException e) {
		}
	}
	
	public static void main(String args[]){
		Test t = new Test();
		t.runTheTask();
	}
}
运行后在future.get()步会阻塞 直到3秒后 返回结果“result”

上面的线程只执行了一个Callable任务。

但有某些情景下需要我们执行好几个Callable任务,并且要获得它们的返回结果,代码就变得很不好控制了

public class Test {
	private final ExecutorService executor = Executors.newFixedThreadPool(3);
	public void runTheTask(){
		Future<String> future1 = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result1";
			}
			
		});
		Future<String> future2 = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result2";
			}
			
		});
		Future<String> future3 = executor.submit(new Callable<String>(){

			@Override
			public String call() throws Exception {
				Thread.sleep(3000);
				return "result3";
			}
			
		});
		try {
			System.out.println( future1.get());
			System.out.println( future2.get());
			System.out.println( future3.get());
		} catch (InterruptedException | ExecutionException e) {
		}
	}
	
	public static void main(String args[]){
		Test t = new Test();
		t.runTheTask();
	}
}

可以看到 上面的代码非常难看而且不好控制。

幸运的是,在这种情况下我们可以使用CompletionService来实现
CompletionService将ExecutorService和BlockingQueue功能融合在了一起。

你可以将Callable任务提交给它来执行,它执行完返回的Future结果会放进BlockingQueue中。

你再用类似于队列操作的take和poll方法来获得已完成的结果。

public class Test2 {
	
	private final ExecutorService executor = Executors.newFixedThreadPool(3);
	
	public void runTasks(){
		CompletionService<String> completionService = new ExecutorCompletionService<String>(executor);
		completionService.submit(new Callable<String>(){
			@Override
			public String call() throws Exception {
				return "result1";
			}
		});
		completionService.submit(new Callable<String>(){
			@Override
			public String call() throws Exception {
				return "result2";
			}
		});
		completionService.submit(new Callable<String>(){
			@Override
			public String call() throws Exception {
				return "result3";
			}
		});
		
		for(int i=0;i<3;i++){
			try {
				Future<String> f = completionService.take();
				System.out.println(f.get());
			} catch (InterruptedException e) {
			} catch (ExecutionException e) {
			}
		}
	}

	public static void main(String[] args) {
		Test2 t = new Test2();
		t.runTasks();
	}
}

我们可以看到我们使用了一个ExecutorService作为参数来初始化 CompletionService。多个 CompletionService可以共享一个ExecutorService。因此可以创建一个对于特定计算私有,又能共享一个ExecutorService的应用。

============================

这里我们再顺带介绍一下线程池的关闭——shutdown()和shutdownNow()方法。

我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。对shutdown()方法的调用可以防止新的任务被提交给这个线程池,当前线程将继续运行在shutdown()被调用之前提交的所有任务

shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。

只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。

我们加入shutdown()和shutdownNow()方法来完善一下上面这个例子:

void shutdownAndAwaitTermination(ExecutorService pool) {
   pool.shutdown(); // 防止新的任务被提交上来
   try {
     // 等待当前已经存在的任务执行完
     if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
       pool.shutdownNow(); // 如果过了指定时间还有任务没有完成,立马停止它们
       // 等待任务响应取消命令
       if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
     }
   } catch (InterruptedException ie) {
     // (Re-)Cancel if current thread also interrupted
     pool.shutdownNow();
     // Preserve interrupt status
     Thread.currentThread().interrupt();
   }
 }

至此,对Executors的简单了解结束。下一篇文章我们将对上一篇文章介绍的ThreadPoolExecutor类做一下更深入的研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值