无论在Java的开发中还是在Android的开发中,线程都占有重要的地位,所以今天就来说说线程池的东西。
一、线程池概述
在Android开发中,我们经常把一个耗时任务放在一个线程中进行执行,目的就是为了避免ANR异常。但是如果我们在一个页面开了很多线程,线程在短时间内执行结束,我们这样频繁的创建线程就降低了系统的运行效率。所以就有了线程池。线程池的作用是什么呢?
线程池会根据系统的环境变量,自动或手动配置一个线程池中的线程数量,使线程的创建和回收达到一个理想的状态,减少了系统资源的消耗,提高系统的效率。这样我们就得出了使用线程池的几个好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 通过线程池对线程进行管理,提高了系统的承受力。(每个线程打越需要1MB内存)。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
二、线程池的基本使用
在jdk1.5中,java给我们提供了java.util.concurrent包,这个包里就是关于我们线程池的使用接口和类。线程池的顶级接口就是Executor接口。它有一个execute(Runnable)方法用来执行所提交的Runnable任务。它只是一个接口,所以需要有它的实现体:
配置线程池是一件比较复杂的工作,如果我们对线程池的原理不是很了解,很容易导致配置的线程池达不到效果。所以系统给我们提供了Executors类,它提供一些静态方法帮助我们创建一些常用的线程池。
- public static ExecutorService newFixedThreadPool(int nThreads):创建固定数目线程的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- public static ExecutorService newCachedThreadPool():创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
- public static ExecutorService newSingleThreadExecutor():创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
测试案例:
我们创建一个Text的线程,用于测试:
class Text implements Runnable{
String name;
public Text(String name){
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--" + name);
}
}
现在我们开始测试线程池的效果:
1、ExecutorService newFixedThreadPool(int nThreads)创建指定大小的线程池数。
public static void main(String[]args){
//我们创建一个指定大小为3的线程池
ExecutorService exServiceFixed = Executors.newFixedThreadPool(3);
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
Text t4 = new Text("t4");
Text t5 = new Text("t5");
exServiceFixed.execute(t1);
exServiceFixed.execute(t2);
exServiceFixed.execute(t3);
exServiceFixed.execute(t4);
exServiceFixed.execute(t5);
exServiceFixed.shutdown();
}
结果:
pool-1-thread-1--t1
pool-1-thread-1--t4
pool-1-thread-1--t5
pool-1-thread-2--t2
pool-1-thread-3--t3
我们可以看到尽管我们用线程池执行了5个线程任务,但是在线程池内部仅仅有我们指定的3个线程在工作,所以达到了提高效率的作用。
2、ExecutorService newCachedThreadPool()创建一个可重用不指定大小的线程池
public static void main(String[]args){
//我们创建一个可重用的线程池
ExecutorService exServiceCached = Executors.newCachedThreadPool();
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
Text t4 = new Text("t4");
Text t5 = new Text("t5");
exServiceCached.execute(t1);
exServiceCached.execute(t2);
exServiceCached.execute(t3);
exServiceCached.execute(t4);
exServiceCached.execute(t5);
exServiceCached.shutdown();
}
运行结果:
pool-1-thread-1--t1
pool-1-thread-2--t2
pool-1-thread-3--t3
pool-1-thread-4--t4
pool-1-thread-5--t5
线程池中开了5个线程,这个线程池的特点就是可重用,不限制大小,数量以JVM在系统中能创建的大小为准。
3、ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建定时的线程池。
例如:
public static void main(String[]args){
//我们创建一个可重用的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
Text t4 = new Text("t4");
Text t5 = new Text("t5");
scheduledExecutorService.schedule(t1, 2000, TimeUnit.MILLISECONDS);
scheduledExecutorService.schedule(t2, 2000, TimeUnit.MILLISECONDS);
}
注意,此时使用的ScheduledExecutorService,它是ExecutorService接口的实现接口。schedule方法,用来定时任务,线程会在指定时间后进行执行。
另外,它还有定时持续的任务,
scheduledExecutorService.scheduleAtFixedRate(t1, 1000,2000, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleAtFixedRate(t2, 1000,2000, TimeUnit.MILLISECONDS);
scheduledExecutorService.scheduleWithFixedDelay(t1, 1000,2000, TimeUnit.MILLISECONDS);
每隔一段时间就去执行任务。
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-2--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
pool-1-thread-2--t1
pool-1-thread-1--t2
4、ExecutorService newSingleThreadExecutor创建单线程线程池
public static void main(String[]args){
//我们创建一个可重用的线程池
ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
Text t1 = new Text("t1");
Text t2 = new Text("t2");
Text t3 = new Text("t3");
executorServiceSingle.execute(t1);
executorServiceSingle.execute(t2);
executorServiceSingle.execute(t3);
}
结果:
pool-1-thread-1--t1
pool-1-thread-1--t2
pool-1-thread-1--t3
我们可以看到只有一个线程在执行我们的任务。
三、线程池的补充知识
这里补充一个知识点就是ExecutorService.submit()方法。这个方法有三个重载:
- Future submit(Callable task);
- Future submit(Runnable task, T result);
- Future
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
什么都没实现,就一个call()方法,根据注释,这个方法用来计算某个结果。
Future的源码结构:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
在Future接口中声明了5个方法,下面依次解释每个方法的作用:
- cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
也就是说Future提供了三种功能:1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
也不复杂,几个方法判断任务是否结束,以及通过get方法获取任务的结果。我们直接看一个例子吧!
public class MainDemo {
public static void main(String[]args){
//我们创建一个可重用的线程池
ExecutorService executorServiceSingle = Executors.newSingleThreadExecutor();
Future<Integer> future = executorServiceSingle.submit(new AddCallable());
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class AddCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
//执行我们的业务逻辑处理
return 2+3;
}
}
我们实现一个Callable接口用来处理我们的任务,然后通过Future来获取任务的结果。果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。这也就是为什么需要捕捉InterruptedException异常的原因。这点是不是跟我们Android的下载任务差不多。开启一个下载任务,然后通过Handler发送的UIThread中进行处理。
同样还有一种组合Callable+FutureTask
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
Thread thread = new Thread(futureTask);
thread.start();
四、总结
本着复习知识点的原则,并没有介绍线程池的配置,大家可以去搜索相关资料学习下,复习这个的知识点,就是准备这几天写个Android网络请求的简单框架使用,所以知识点也知识简单介绍了用法,具体的深入分析没做。
参考文章:
http://blog.csdn.net/sd0902/article/details/8395677
http://www.cnblogs.com/dolphin0520/p/3949310.html
http://www.cnblogs.com/dolphin0520/p/3932921.html
http://www.iteye.com/topic/366591
======================================
作者:mr_dsw
地址:http://blog.csdn.net/mr_dsw
理念:转载注明出处,分享是进步的源泉
======================================