Executors

  • Executors与线程池
  • 例子一:线程池的创建与使用
  • 例子二:更为复杂的情况
  • 总结
  • executors的常用API一览
  • 创建线程池
  • 线程池调用线程
  • 关闭线程池
  • 线程池的查询


Executors与线程池

Executorsjava.util.concurrent提供的一个并发框架,其中集成的一个线程池很有用,它避免了传统运行中自己创建线程的麻烦,改为将已经创建好的线程提供给用户使用,在简化了线程创建操作的同时,实现了线程的复用。

例子一:线程池的创建与使用

用一个简单的例子来演示线程池的创建与使用:

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class Server
{
    // 线程池
	private ThreadPoolExecutor executor;
	
	public Server()
	{
		// 动态线程池,容量随任务量增加自行管理
		executor = (ThreadPoolExecutor)Executors.newCachedThreadPool();
		// 固定大小线程池
		//executor = (ThreadPoolExecutor)Executor.newFixedThreadPool(5);
	}
	
	// 向线程提交任务
	public void submitTask(Task task)
	{
		System.out.printf("Server: A new task has arrived\n");
		executor.execute(task); //执行 无返回值
		
		System.out.printf("Server: Pool Size: %d\n", executor.getPoolSize());
		System.out.printf("Server: Active Count: %d\n", executor.getActiveCount());
		System.out.printf("Server: Completed Tasks: %d\n", executor.getCompletedTaskCount());
	}
	
	public void endServer()
	{
		executor.shutdown();
	}
}

这里利用Server将线程池封装在里面,注意几个线程池的操作函数:executor.execute()(参数是一个线程类)和 executor.shutdown()(相当于向线程池里面的每一个线程调用了一个interrupt()),以及线程池内的一些查询语句
Task是一个自定义的线程类,其内容如下:

import java.util.Date;

/**
 * 
 * Task 任务类
 *
 */
public class Task implements Runnable {
	
	private String name;
	
	public Task(String name)
	{
		this.name = name;
	}
	
	public void run()
	{
		try
		{
			Long duration = (long)(Math.random()*1000);
			System.out.printf("%s: Task %s: Doing a task during %d seconds\n", Thread.currentThread().getName(),Thread.currentThread().getName(),duration);
			Thread.sleep(duration);
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		
		System.out.printf("%s: Task %s: Finished on: %s\n", Thread.currentThread().getName(),Thread.currentThread().getName(),Thread.currentThread().getName());
	}
	
}

main函数的调用如下:

public class Main {

	public static void main(String[] args) throws InterruptedException {
		// 创建一个执行服务器
		Server server = new Server();
		
		// 创建100个任务,并发给执行器,等待完成
		for (int i = 0; i < 100; i++)
		{
			Task task = new Task("Task " + i);
			Thread.sleep(10);
			server.submitTask(task);
		}
		
		server.endServer();
		
	}

}

上面的例子中,main函数启动了100个线程,并将其全部放入server类里的线程池中,其中创建线程管理线程的细节我们均可以不用关心。

例子二:更为复杂的情况

上面的例子中我们调用的是一个实现Runnable接口的类,下面这个例子是一个实现Callable接口的线程类

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.Future;

public class SumTest {

	public static void main(String[] args)
	{
		
		// 执行线程池
		ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(4);
		// 使用固定大小的线程池可以防止启动过多线程导致计算机负荷过大,一般选用cpu核数2到4倍的作为线程大小
		
		List<Future<Integer>> resultList = new ArrayList<>();
		
		// 统计1-1000总和, 分成10个任务计算, 提交任务
		for (int i =0; i < 10; i++)
		{
			SumTask calculator = new SumTask(i*100+1, (i+1)*100);
			Future<Integer> result = executor.submit(calculator);
			resultList.add(result);
		}
		
		// 每隔50毫秒,轮询等待10个任务结束
		do {
			System.out.printf("Main: 已知完成多少个任务: %d\n", executor.getCompletedTaskCount());
			for (int i = 0; i < resultList.size(); i++)
			{
				Future<Integer> result = resultList.get(i);
				System.out.printf("Main: Task %d: %s\n", i, result.isDone());
				
			}
			try
			{
				Thread.sleep(50);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		} while (executor.getCompletedTaskCount() < resultList.size());
		
		// 所有任务都已经结束了,综合计算结果
		int total = 0;
		for (int i = 0; i < resultList.size(); i++)
		{
			Future<Integer> result = resultList.get(i);
			Integer sum = null;
			try
			{
				sum = result.get();
				total = total + sum;
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}catch (ExecutionException e)
			{
				e.printStackTrace();
			}
		}
		System.out.printf("1-1000的总和:" + total);
		
		// 关闭线程池
		executor.shutdown();
	}
}

上面这段程序主要完成的任务是,将一个计算任务平均分成10份,交给10个线程去做,最后统计结果。主线程每隔50毫秒会对每一个线程进行检查,看是否运行完成,当所有线程(任务)都运行完成后才会结束。这里使用的线程类SumTask是一个实现callable接口的类,他的返回值需要使用Future类来接收,并且通过调用Future类的isDone方法可以很容易的查看线程是否以及运行结束。注意到这里线程池没有封装,他启动线程的方式和例子一中不同。例子一中线程启动的方式是executor.execute(Runnable command),而这个例子里用了executor.submit(Callable command)。其主要区别是前者用于无返回值的线程类Runnable,而后者适用于有返回值的线程类Callable
我们来看一下调用的线程类SumTask

import java.util.Random;
import java.util.concurrent.Callable;

public class SumTask implements Callable<Integer> {
	private int startNumber;
	private int endNumber;
	
	public SumTask(int startNumber, int endNumber)
	{
		this.startNumber = startNumber;
		this.endNumber = endNumber;
	}
	
	@Override
	public Integer call() throws Exception
	{
		int sum = 0; 
		for (int i = startNumber; i <= endNumber; i++)
		{
			sum = sum + i;
		}
		
		Thread.sleep(new Random().nextInt(1000));
		
		System.out.printf("%s: %d\n", Thread.currentThread().getName(), sum);
		return sum;
	}
}

它主要就是把从startNumberendNumber的所有数字加在一起,并把它们的和返回去。中间调用了一个sleep来模拟程序运行的时间。

总结

Executors是由java.util.conurrent(J.U.C)提供的非常好用的并发框架,它利用线程池很好的优化了线程的管理和使用,向使用者屏蔽了线程创建,启动和死亡的多种细节,并且实现了线程的复用,提高了多线程的运行效率。

executors的常用API一览

创建线程池

ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(4);// 固定大小线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newCachedThreadPool();// 动态大小线程池

线程池调用线程

executor.(Runnable command); //调用Runnable线程类,无返回值
Future<> a = executor.submit(Callable command);//调用Callable类,用Futured对象接收返回值

关闭线程池

executor.shutdown();

线程池的查询

executor.getPoolSize();// 返回线程池大小,主要用于动态线程池
 executor.getActiveCount();// 返回活跃的线程个数
 executor.getCompletedTaskCount();// 返回已经完成的任务个数,注意是任务而非线程