构建一个新的线程是有一定代码的,因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池(thread pool)。一个线程池中包含许多准备运行的空闲线程。将Runnable是交给线程池,就会有一个线程调用
run方法。当run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。
另一个使用线程池的理由是减少并发线程的数目。创建大量线程会大大降低性能甚至使虚拟机崩溃。如果有一个会创建许多线程的算法,应该使用一个线程数“固定的”线程池以限制并发线程的总数。
执行器(Executor)类有许多静态工厂方法用来构建线程池。如下:
方 法 描 述
newCachedThreadPool 必要时创建新线程:空闲线程会被保留60秒
newFixedThreadPool 该池包含固定数量的线程;空闲线程会一直保留
newSingleThreadExecutor 只有一个线程的“池”,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程)
newScheduledThreadPool 用于预定执行而构建的固定线程池,替代java.util.Timer
newSingleThreadScheduledExecutor 用于预定执行而构建的单线程“池”
线程池
newCachedThreadPool方法构建一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。newFixedThreadPool方法构建一个具有固定大小的线程池。如果提交的任务数多于空闲的线程数,那么把得不到服务的。任务旋转到队列中。当其他任务完成以后再运行它们。newSingleThreadExecutor是一个退化了的大小为1的线程池:由一个线程执行提交的任务,一个接一个。这3个方法返回实现了ExecutorService接口的ThreadPoolExecutor类的对象。
可用下面的方法之一将一个Runnable对象或Callable对象提交给ExecutorService:
java.util.concurrent.ExecutorService
Future<?> submit(Runnable task) 返回Future<?> 可以调用isDone、cancel或isCancelled。get方法在完成的时候只是简单的返回null
Future<T> submit(Runnable task,T result) 提交一个Runnable对象,并且Futrue的get方法在完成的时候返回指定的result对象。
Future<T> submit(Callable<T> task) 提交一个Callable,并且返回的Futrue对象将在计算结果准备好的时候的得到它。
该线程池会在方便的时候尽早执行提交的任务。调用submit时,会得到一个Future对象,可用来查询该任务的状态。当用完一个线程池的时候,调用shutdown。该方法启动该池的关闭序列。被关闭的执行器不再接受新的任务。当所有的任务都完成以后,线程池中的线程死亡。另一种方法是调用shutdownNow。该池取消尚未开始的所有任务并试图中断正在运行的线程。
总结使用连接池时应该做的事:
1)调用Executors类中静态的方法newCachedThreadPool或newFixedThreadPool。
2)调用submit提交Runnable或Callable对象。
3)如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象。
4)当不再提交任何任务时,调用shutdown。
例如,前几篇笔记里的程序例子产生了大量的生命期很短的线程,每个目录产生一个线程。 以下代码将演示使用一个线程池来运行任务。(出于信息方面的考虑,这个程序打印出执行中池中最大的线程数。但是不能通过ExecutorService这个接口得到这一信息。因此必须将该pool对象强制转换为ThreadPoolExecutor类对象)。
package test.ThreadPool; import java.io.File; import java.util.Scanner; import java.util.concurrent.*; /** * Created by Administrator on 2017/11/27. */ public class ThreadPoolTest { public static void main(String[] args) { Scanner in=new Scanner(System.in); System.out.print("输入目前路径:"); String directory=in.nextLine(); System.out.print("输入关键字:"); String keyword=in.nextLine();
//1.调用Executors类中静态的方法newCachedThreadPool ExecutorService pool= Executors.newCachedThreadPool(); MatchCounterThreadPool counter=new MatchCounterThreadPool(new File(directory),keyword,pool);
//2.调用submit提交Runnable或Callable对象。
//3.如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象。 Future<Integer> result=pool.submit(counter); try{ System.out.println(result.get()+" 匹配文件"); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
//4.当不再提交任何任务时,调用shutdown。 pool.shutdown();
//返回线程池在该执行器生命周期中的最大尺寸 int largestPoolSize=((ThreadPoolExecutor)pool).getLargestPoolSize(); System.out.println("线程池大小="+largestPoolSize); } }
package test.ThreadPool;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/**
* 此任务对包含给定关键字的目录及其子目录中的文件进行计数
*/
public class MatchCounterThreadPool implements Callable<Integer> {
private File directory;
private String keyword;
private ExecutorService pool;
private int count;
/**
*
* @param directory 开始查询的文件
* @param keyword 寻找关键字
* @param pool 用于提交子任务的线程池
*/
public MatchCounterThreadPool(File directory, String keyword, ExecutorService pool) {
this.directory=directory;
this.keyword=keyword;
this.pool=pool;
}
@Override
public Integer call() throws Exception {
count=0;
try{
File[] files=directory.listFiles();
List<Future<Integer>> results=new ArrayList<>();
for (File file:files
) {
if (file.isDirectory()){
MatchCounterThreadPool counter=new MatchCounterThreadPool(file,keyword,pool);
Future<Integer> result=pool.submit(counter);
results.add(result);
}
else {
if(search(file)){
count++;
}
}
for (Future<Integer> result:results
) {
try{
count+=result.get();
}
catch (ExecutionException e){
e.printStackTrace();
}
}
}
}catch (InterruptedException e){
}
return count;
}
/**
* 搜索一个给定关键字的文件
*
* @param file
* @return
*/
public boolean search(File file) {
try {
try (Scanner in = new Scanner(file, "gbk")) {
boolean isFound = false;
while (!isFound && in.hasNextLine()) {
String line = in.nextLine();
if (line.contains(keyword)) {
isFound = true;
}
}
return isFound;
}
} catch (IOException e) {
return false;
}
}
}
java.util.concurrent.Executors
- ExecutorService newCachedThreadPool()
返回一个带缓存的线程池,该池在必要的时候创建线程,在线程空闲的60秒后终止线程。
- ExcuorService newFixedThreadPool(int threads)
返回一个线程池,该池中的线程数由参数指定。
- ExecutorService newSingleThreadExecutor()
返回一个执行器,它在一个单个的线程中依次执行各个任务。
java.util.concurrent.ThreadPoolExecutor
- int getLargestPoolSize() 返回线程池在该执行器生命周期中的最大尺寸。
预定执行
ScheduledExecutorService接口具有为预定执行(Scheduled Execution)或重复执行任务而设计的方法。它是一种允许使用线程池机制的java.util.Timer的泛化。Executors类的newScheduledThreadPool和newSingleThreadScheduledExecutor方法将返回实现了ScheduledExecutorService接口对象。
可以预定Runnable或Callable在初始的延迟之后只运行一次。也可以预定一个Runnable对象周期性地运行。
java.util.concurrent.Executors
- ScheduledExecutorService newSheduledThreadPool(int threads) 返回一个线程池,它使用给定的线程数来调度任务。
- ScheduledExecutorService newSingleThreadScheduledExecutor() 返回一个执行器,它在一个单独的线程中调度任务。
java.util.concurrent.ScheduledExecutorService
- ScheduledFuture<V> schedule(Callable<V> task,long time,TimeUnit unit)
- ScheduledFuture<?> schedule(Runnable<?> task,long time,TimeUnit unit) 预订在指定的时间之后执行任务。
- ScheduledFuture<V> scheduleAtFixedRate(Runnable task,long initialDelay,long period,TimeUnit unit) 预定在初始的延迟结束后,周期性地运行给定的任务,周期长度是period。
- ScheduledFuture<V> scheduleWithFixedDelay(Runnable task,long initialDelay,long period,TimeUnit unit) 预定在初始的延迟结束后周期性地给定的任务,在一次调用完成和下一次调用开始之间有长度为delay的延迟。
Fork-Join框架
一个Web服务器可能会为每个连接分别使用一个线程。另外一些应用可能对每个处理器内核分别使用一个线程,来完成计算密集型任务,如图像或视频处理。java SE 7中引入了fork-join框架,专门用来支持后一类应用。
在后台,fork-join框架使用了一种有效的智能方法来平衡可用线程的工作负载,这种方法称为工作密取(work stealing)。每个工作线程都有一个双端队列(deque)来完成任务。一个工作线程将子任务压入其双端队列的队头。(只有一个线程可以访问队头,所以不需要加锁)一个工作线程空闲时,它会从另一个双端队列的队尾"密取”一个任务。由于大的子任务都在队尾,这种密取很少出现。
package test.ForkJoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class CountTask extends RecursiveTask<Integer> {
public static final int threshold=2;
private int start;
private int end;
public CountTask(int start,int end){
this.start=start;
this.end=end;
}
@Override
protected Integer compute() {
int sum=0;
//如果任务足够小就计算任务
boolean canCompute=(end-start)<=threshold;
if (canCompute){
for (int i = start; i <=end ; i++) {
sum+=i;
}
}
else {
//如果任务大于阈值,就分裂成两个子任务计算
int middle=(start+end)/2;
CountTask leftTask=new CountTask(start,middle);
CountTask rightTask=new CountTask(middle+1,end);
//执行子任务
leftTask.fork();
rightTask.fork();
//等待任务执行结束合并其结果
int leftResult=leftTask.join();
int rightResult=rightTask.join();
//合并子任务
sum=leftResult+rightResult;
}
return sum;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool=new ForkJoinPool();
//生成一个计算任务,计算1+2+3+4
CountTask task=new CountTask(1,100);
//执行一个任务
Future<Integer> result=forkJoinPool.submit(task);
System.out.println(result.get());
}
}