【Java并发编程六】线程池
一、概述
在执行并发任务时,我们可以把任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程,只要池里有空闲的线程,任务就会分配一个线程执行。在线程池的内部,任务被插入一个阻塞队列(BlockingQueue),线程池里的线程会去取这个队列里的任务。
利用线程池有三个好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
二、线程池的使用
1、创建线程池
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
创建一个线程池需要的几个参数:
- corePoolSize:线程池的基本大小,当提交一个任务到线程池,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于corePoolSize时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
- maximumPoolSize:线程池最大大小,线程池允许创建的最大线程数,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。
- keepAliveTime:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。
- unit:线程活动保持时间的单位
- workQueue:任务队列,用于保存等待执行的任务的阻塞队列,可以选择以下几个队列:
1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
3、SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
4、PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
- threadFactory:用于设置创建线程的工厂,可以通过线程工程给每个创建出来的线程设置更有意义的名字。
- handler:饱和策略,当线程池和队列都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。
此外,我们还可以通过调用Ececutors中的某个静态工厂方法来创建一个线程池(它们的内部实现原理都是相同的,仅仅是使用了不同的工作队列或线程池大小):
newFixedThreadPool:创建一个定长的线程池,每当提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不在变化
newCachedThreadPool:创建一个可缓存的线程池,如果当前的线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需求增加时,它可以灵活的添加新的线程,并不会对池的长度做任何限制
newSingleThreadPool:创建一个单线程化的executor,它只会创建唯一的工作者线程来执行任务
newScheduledThreadPool:创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer
2、向线程池提交任务
可以使用execute向线程池提交任务:
public class Test2
{
public static void main(String[] args)
{
BlockingQueue<Runnable> workQueue=new LinkedBlockingDeque<Runnable>();
ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor(2, 3, 60, TimeUnit.SECONDS, workQueue);
poolExecutor.execute(new Task1());
poolExecutor.execute(new Task2());
poolExecutor.shutdown();
}
}
class Task1 implements Runnable
{ public void run()
{
System.out.println("执行任务1");
}
}
class Task2 implements Runnable
{ public void run()
{
System.out.println("执行任务2");
}
}
也可以使用submit方法来提交任务,它会返回一个future,我们可以通过这个future来判断任务是否执行成功,通过future的get方法获取返回值,get方法会阻塞直到任务完成。
public class Test3
{
public static void main(String[] args) throws InterruptedException, ExecutionException
{
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<String>> resultList = new ArrayList<Future<String>>();
// 创建10个任务并执行
for (int i = 0; i < 10; i++)
{
// 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
Future<String> future = executorService.submit(new TaskWithResult(i));
resultList.add(future);
}
for (Future<String> future : resultList)
{
while (!future.isDone());// Future返回如果没有完成,则一直循环等待,直到Future返回完成
{
System.out.println(future.get()); // 打印各个线程(任务)执行的结果
}
}
executorService.shutdown();
}
}
class TaskWithResult implements Callable<String>
{
private int id;
public TaskWithResult(int id)
{
this.id = id;
}
public String call() throws Exception
{
return "执行结果"+id;
}
}
3、线程关闭
可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同,shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
三、线程池的执行过程
整个ThreadExecutor的任务处理经过下面4个步骤,如下图所示:

1、如果当前的线程数<corePoolSize,提交的Runnable任务,会直接作为new Thread的参数,立即执行,当提交的任务数超过了corePoolSize,就进入第二部操作
2、将当前的任务提交到BlockingQueue阻塞队列中,如果Block Queue是个有界队列,当队列满了之后就进入第三步
3、如果poolSize < maximumPoolsize时,会尝试new 一个Thread的进行救急处理,执行对应的runnable任务
4、如果第三步也无法处理,就会用RejectedExecutionHandler来做拒绝处理
四、执行定时及周期性任务
Timer工具管理任务的定时以及周期性执行。示例代码如下:
public class TimerTest
{
final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args)
{
TimerTask timerTask1=new TimerTask()
{
@Override
public void run()
{
System.out.println("任务1执行时间:"+sdf.format(new Date()));
try
{
Thread.sleep(3000);//模拟任务1执行时间为3秒
}
catch (InterruptedException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
};
System.out.println("当前时间:"+sdf.format(new Date()));
Timer timer=new Timer();
timer.schedule(timerTask1, new Date(),4000); //间隔4秒周期性执行
}
}
执行结果:

可以看到上述任务1以4秒为间隔周期性执行。但是Timer存在一些缺陷,主要是下面两个方面的问题:
缺陷1:Timer只创建唯一的线程的来执行所有的Timer任务,如果一个time任务的执行很耗时,会导致其他的TimeTask的时效准确性出问题。看下面的例子:
public class TimerTest
{
final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args)
{
TimerTask timerTask1=new TimerTask()
{
@Override
public void run()
{
System.out.println("任务1执行时间:"+sdf.format(new Date()));
try
{
Thread.sleep(10000);
}
catch (InterruptedException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
};
TimerTask timerTask2=new TimerTask()
{
@Override
public void run()
{ System.out.println("任务2执行时间:"+sdf.format(new Date()));
}
};
System.out.println("当前时间:"+sdf.format(new Date()));
Timer timer=new Timer();
timer.schedule(timerTask1, new Date(),1000); //间隔1秒周期性执行
timer.schedule(timerTask2, new Date(),4000); //间隔4秒周期性执行
}
}
执行结果:

缺陷2:如果TimeTask抛出未检查的异常,Timer将产生无法预料的行为。Timer线程并不捕获线程,所有TimerTask抛出的未检查的异常会终止timer线程。看下面的代码:
public class TimerTest2
{
final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args)
{
TimerTask timerTask1=new TimerTask()
{
@Override
public void run()
{
System.out.println("任务1执行时间:"+sdf.format(new Date()));
throw new RuntimeException();
}
}; TimerTask timerTask2=new TimerTask()
{
@Override
public void run()
{
System.out.println("任务2执行时间:"+sdf.format(new Date()));
}
};
System.out.println("当前时间:"+sdf.format(new Date()));
Timer timer=new Timer();
timer.schedule(timerTask1, new Date(),1000); //周期1秒执行任务1
timer.schedule(timerTask2, new Date() ,3000); //周期3秒执行任务2 }
}
执行结果为:

针对上述的两个问题,我们可以使用ScheduledThreadPoolExecutor来作为Timer的替代。
针对问题1,有下面代码:
public class ScheduledThreadPoolExecutorTest
{
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args)
{
TimerTask timerTask1 = new TimerTask()
{ @Override
public void run()
{
System.out.println("任务1执行时间:" + sdf.format(new Date()));
try
{
Thread.sleep(10000);
} catch (InterruptedException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
};
TimerTask timerTask2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("任务2执行时间:" + sdf.format(new Date()));
}
};
System.out.println("当前时间:" + sdf.format(new Date()));
ScheduledThreadPoolExecutor poolExecutor=new ScheduledThreadPoolExecutor(2);
poolExecutor.scheduleAtFixedRate(timerTask1, 0, 1000,TimeUnit.MILLISECONDS);
poolExecutor.scheduleAtFixedRate(timerTask2, 0, 4000,TimeUnit.MILLISECONDS);
}
}
执行的结果为:

针对问题2,有下面代码:
public class ScheduledThreadPoolExecutorTest2
{
final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args)
{
TimerTask timerTask1 = new TimerTask()
{ @Override
public void run()
{
System.out.println("任务1执行时间:" + sdf.format(new Date()));
throw new RuntimeException();
}
};
TimerTask timerTask2 = new TimerTask()
{
@Override
public void run()
{ System.out.println("任务2执行时间:" + sdf.format(new Date()));
}
};
System.out.println("当前时间:" + sdf.format(new Date()));
ScheduledThreadPoolExecutor poolExecutor=new ScheduledThreadPoolExecutor(2);
poolExecutor.scheduleAtFixedRate(timerTask1, 0, 1000, TimeUnit.MILLISECONDS);
poolExecutor.scheduleAtFixedRate(timerTask2, 0, 2000, TimeUnit.MILLISECONDS);
}
}
执行结果为:

五、参考资料
1、Java并发编程实践
【Java并发编程六】线程池的更多相关文章
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程池的使用(转)
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- (转)Java并发编程:线程池的使用
背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...
- Java并发编程:线程池的使用(转载)
转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
- Java并发编程:线程池的使用(转载)
文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
- [转]Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- 【转】Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- 13、Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程之线程池的使用
1. 为什么要使用多线程? 随着科技的进步,现在的电脑及服务器的处理器数量都比较多,以后可能会越来越多,比如我的工作电脑的处理器有8个,怎么查看呢? 计算机右键--属性--设备管理器,打开属性窗口,然 ...
- Java并发编程之线程池及示例
1.Executor 线程池顶级接口.定义方法,void execute(Runnable).方法是用于处理任务的一个服务方法.调用者提供Runnable 接口的实现,线程池通过线程执行这个 Runn ...
随机推荐
- 利用Xmanager Enterprise 5的passive显示远程linux主机图形化信息
问题描述: 最初的需求是,安装oracle数据(第一次安装都是图形化linxu进去一步步操作,后续发现可以命令静默安装不调用图形化,学习就是步步入深,方得始终),最初实现window弹出linux主机 ...
- Retrofit 2.0 使用详细教程
文章来自:https://blog.csdn.net/carson_ho/article/details/73732076 前言 在Andrroid开发中,网络请求十分常用 而在Android网络请求 ...
- 【tp5】tp5实现空模块、空控制器、空操作的页面404跳转
写在最开始:本教程适用于tp5路由的[强制模式+半pathinfo模式+pathinfo模式],通用. 比网上的[通过路由去控制空模块更强力.更全面]. PS:路由控制空模块缺点:仅仅在[强制模式下才 ...
- 基于Centos搭建 Hadoop 伪分布式环境
软硬件环境: CentOS 7.2 64 位, OpenJDK- 1.8,Hadoop- 2.7 关于本教程的说明 云实验室云主机自动使用 root 账户登录系统,因此本教程中所有的操作都是以 roo ...
- swift4.0 对 afn 进行二次封装
先将 afn 用pod导入到 工程中 创建一个类 ZHttpTools 继承自 AFHTTPSessionManager 一般我们不希望网络请求同时有多个存在,所以我们将这个工具类 设计成单例 代 ...
- Self-Host
寄宿Web API 不一定需要IIS 的支持,我们可以采用Self Host 的方式使用任意类型的应用程序(控制台.Windows Forms 应用.WPF 应用甚至是Windows Service) ...
- MySQL权限和用户安全
MySQL访问控制分为两个阶段阶段1: 检查用户是否可以连接到mysql server 阶段2: 检查用户执行的sql是否已经被授权
- layui table 根据条件改变更换表格颜色 高亮显示 数据筛选
请问想让当layui表格的某个字段符合某个条件的时候,让该行变颜色.这样可以实现么. layui数据表格怎么更换表格颜色 layui表格 通过判断某一行中的某一列的值进行设置这一行的颜色 LayUI之 ...
- 开发第一个flutter程序 hello world
上一篇咱们配置了flutter的基本环境之后,那么本篇就来讲一下如何开发第一个小应用hello world 双击打开 android studio 选择plugins 找到 fultter 并且下载安 ...
- iOS的动态代理模式的实现
动态代理模式的应用很多,特别是在不能修改被代理类的前提下,要对执行某些方法时需要打log或者捕捉异常等处理时,是一个非常方便的方法.只需要少量修改客户端(场景类)代码和添加一个代理类就可以实现,这个符 ...