1.最简单入门
public class ThreadTest {
@Test
public void testExecute()
{
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++)
{
executorService.execute(new MyThread());
}
executorService.shutdown();
}
}
class MyThread implements Runnable
{
private static final Logger logger = LoggerFactory.getLogger(MyThread.class);
public void run()
{
logger.info("I am running...");
}
}
执行结果为:
2015/06/17 20:02:56,594 - INFO - pool-1-thread-2 - I am running...
2015/06/17 20:02:56,595 - INFO - pool-1-thread-1 - I am running...
2015/06/17 20:02:56,595 - INFO - pool-1-thread-1 - I am running...
2015/06/17 20:02:56,597 - INFO - pool-1-thread-3 - I am running...
2015/06/17 20:02:56,597 - INFO - pool-1-thread-3 - I am running...
说明线程池已经起了作用。注意:其中的shutdown并不是关闭线程池,而是不再接受新的执行任务,当其中线程执行完成之后,再关闭
2.相关概念
(1)ExecutorService
看一下ExecutorService的源码:
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
}
而ExecutorService又继承了Executor接口
public interface Executor {
void execute(Runnable command);
}
所以,ExecutorService就是线程池的实际执行操作接口,那么有了接口具体的实现是什么呢?
jdk给我们定义了多个线程池的具体实现,可以通过Executors来获得
(2)Executors
用来获得线程池的工具类
1)创建固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用线程的、固定线程数的线程池
表示核心线程和最大线程数都是nThreads,也就是说如果创建的任务数超过了nThreads,那么就会将任务抛弃
2)创建不限容量但是会过期的线程池
public static ExecutorService newCachedThreadPool()
创建一个不限大小的线程池,如果其中的线程超过60秒闲置,就会被线程池回收
3)创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor()
其中只有一个线程(3)线程执行
1)使用execute方法
void execute(Runnable command);
最核心的方法,用来执行一个线程
2)使用submit方法
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
如果使用Callable来实现线程,那么可以用submit来获得返回的参数,但是注意:submit会吃掉异常!!!必须在通过Future.get()来获取出现的异常正常情况:
public class ThreadTest {
private static final Logger logger = LoggerFactory.getLogger(ThreadTest.class);
@Test
public void testExecute()
{
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++)
{
Future<String> result = executorService.submit(new MyThread());
try
{
String resString = result.get();
logger.info(resString);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
e.printStackTrace();
}
}
executorService.shutdown();
}
}
class MyThread implements Callable<String>
{
private static final Logger logger = LoggerFactory.getLogger(MyThread.class);
public String call() throws Exception {
logger.info("I am running...");
return "success";
}
}
2015/06/17 21:36:06,789 - INFO - pool-1-thread-1 - I am running...
2015/06/17 21:36:06,789 - INFO - pool-1-thread-1 - I am running...
2015/06/17 21:36:06,791 - INFO - main - success
2015/06/17 21:36:06,791 - INFO - main - success
2015/06/17 21:36:06,797 - INFO - pool-1-thread-2 - I am running...
2015/06/17 21:36:06,797 - INFO - pool-1-thread-2 - I am running...
2015/06/17 21:36:06,797 - INFO - main - success
2015/06/17 21:36:06,797 - INFO - main - success
2015/06/17 21:36:06,798 - INFO - pool-1-thread-3 - I am running...
2015/06/17 21:36:06,798 - INFO - main - success
异常情况:
public class ThreadTest {
private static final Logger logger = LoggerFactory.getLogger(ThreadTest.class);
@Test
public void testExecute()
{
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++)
{
executorService.submit(new MyThread());
}
executorService.shutdown();
}
}
class MyThread implements Callable<String>
{
private static final Logger logger = LoggerFactory.getLogger(MyThread.class);
public String call() throws Exception {
int a = 3/0;
logger.info("I am running...");
return "success";
}
}
按理说应该抛出除零异常,但是实际是程序没有任何输出,单元测试时可以通过的。
所以必须自己手动捕获异常:
public class ThreadTest {
private static final Logger logger = LoggerFactory.getLogger(ThreadTest.class);
@Test
public void testExecute()
{
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++)
{
Future<String> result = executorService.submit(new MyThread());
try
{
String resString = result.get();
logger.info(resString);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
catch (ExecutionException e)
{
//如果一个线程出错,则所有线程都关闭
executorService.shutdownNow();
e.printStackTrace();
}
}
executorService.shutdown();
}
}
class MyThread implements Callable<String>
{
private static final Logger logger = LoggerFactory.getLogger(MyThread.class);
public String call() throws Exception {
int a = 3/0;
logger.info("I am running...");
return "success";
}
}
(3)线程池关闭
1)shutdown
不是关闭线程池,只是不能接受新的任务了,它会等待所有任务执行完毕
2)shutdownNow
真正的关闭,会关闭其中的所有线程
3.自定义线程池
(1)使用的方法
我们创建线程池一般都是通过ThreadPoolExecutor来实现的public class ThreadPoolExecutor extends AbstractExecutorService {
.....
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
...
}
这里简单看一下继承关系:
(2)构造参数意义
1)corePoolSize核心池大小。默认情况下,在创建了线程池之后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize之后,就会把到达的任务放到缓存队列中。
2)maximumPoolSize
线程池最大线程数,表示线程池中最多能创建多少个线程
3)keepAliveTime
表示线程没有任务执行时最多保持多久时间终止。
默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用。也就是说当线程池中的线程数量大于corePoolSize时,如果一个线程空闲时间达到keepAliveTime,则会终止,直到线程池中的线程数目不超过corePoolSize
4)uint
和keepAliveTime配合使用,表示时间单位,有七种取值。
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5)workQueue
一个阻塞队列,用来存储等待执行的任务。一般有以下几种选择
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
一般使用LinkedBlockingQueue
6)threadFactory
线程工厂,用来创建线程
7)handler
表示当拒绝处理任务时的策略,有以下几种取值
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
(3)主要流程
1)corePoolSize与maximumPoolSize说明
corePoolSize翻译成核心池大小,实际上就是线程池的大小。举个例子:
加入有一个工厂,里面有10个工人,每个工人只能同时做一件任务。
开始时10个工人都是空闲的,来了任务就分配给空闲的工人做;
当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;
如果说新任务的增长速度远远大于工人做任务的速度,那么这时工厂主管可能会想补救措施,比如新招聘4个临时工来做,
然后就将新任务分配给临时工来做;
如果说14个工人做任务的速度还是不够,那么主管就要考虑不再接收新任务或者抛弃前面的一些任务了。
例子中,corePoolSize就是10,而maximumPoolSize就是(10+4)=14
2)具体总结
- 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
- 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。