多线程系列第(七)篇---线程池
技术背景
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因。比如大家所熟悉的数据库连接池正是遵循这一思想而产生的,本文将介绍的线程池技术同样符合这一思想。
--------------以上内容来自百度百科-----------------------
java提供的几种线程池
先介绍几个类
Executors
提供一系列静态方法用于创建线程池,返回的线程池对象都继承了ExecutorService接口
ExecutorService
线程池对象,用于管理线程池中的线程
ScheduledExecutorService
具有定时效果的线程池对象
线程池介绍
newCachedThreadPool()
创建一个弹性线程池,自动回收不使用的线程(终止并从缓存中移除那些已有 60 秒钟未被使用的线程),(在无可用线程的情况下)自动的为新来的task创建新线程。
newSingleThreadExecutor()
创建一个只有一个线程的线程池,一个任务执行完毕,下一个任务才能接着执行
newFixedThreadPool(int nThreads)
创建一个固定线程数量的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
newScheduledThreadPool(int corePoolSize)
创建一个定长线程池,支持定时及周期性任务执行。注意corePoolSize,当需要的线程超过corePoolSize时,还是会新建线程,只不过超过的部分会在空闲时间回收
代码示例
CachedThreadPool代码示例
public class ThreadPool_CacheDemo {
public static void main(String[] args) {
ExecutorService es1 = Executors.newCachedThreadPool();
es1.execute(new MyTask("任务1"));
es1.execute(new MyTask("任务2"));
es1.execute(new MyTask("任务3"));
es1.execute(new MyTask("任务4"));
es1.execute(new MyTask("任务5"));
}
}
class MyTask implements Runnable {
private String taskName;
public MyTask(String taskName){
this.taskName=taskName;
}
public void run() {
System.out.println(taskName+"执行时间:"+System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果
任务4执行时间:1507874348487 线程名:pool-1-thread-4
任务3执行时间:1507874348487 线程名:pool-1-thread-3
任务1执行时间:1507874348487 线程名:pool-1-thread-1
任务5执行时间:1507874348487 线程名:pool-1-thread-5
任务2执行时间:1507874348487 线程名:pool-1-thread-2
可以看出5个任务几乎是同一时间执行的,并且每个任务都对应一个线程
SingleThreadPool代码示例
public class ThreadPool_SingleDemo {
public static void main(String[] args) {
ExecutorService es2 = Executors.newSingleThreadExecutor();
es2.execute(new MyTask("任务1"));
es2.execute(new MyTask("任务2"));
es2.execute(new MyTask("任务3"));
es2.execute(new MyTask("任务4"));
es2.execute(new MyTask("任务5"));
}
}
运行结果
任务1执行时间:1507874414908 线程名:pool-1-thread-1
任务2执行时间:1507874416909 线程名:pool-1-thread-1
任务3执行时间:1507874418910 线程名:pool-1-thread-1
任务4执行时间:1507874420912 线程名:pool-1-thread-1
任务5执行时间:1507874422913 线程名:pool-1-thread-1
可以看出每个任务和上个任务执行时间相差差不多2秒钟,并且只有一个线程
FixedThreadPool代码示例
public class ThreadPool_FixedDemo {
public static void main(String[] args) {
ExecutorService es3 = Executors.newFixedThreadPool(2);
es3.execute(new MyTask("任务1"));
es3.execute(new MyTask("任务2"));
es3.execute(new MyTask("任务3"));
es3.execute(new MyTask("任务4"));
es3.execute(new MyTask("任务5"));
}
}
运行结果
任务2执行时间:1507874489559 线程名:pool-1-thread-2
任务1执行时间:1507874489559 线程名:pool-1-thread-1
任务3执行时间:1507874491565 线程名:pool-1-thread-2
任务4执行时间:1507874491565 线程名:pool-1-thread-1
任务5执行时间:1507874493569 线程名:pool-1-thread-2
可以看出,每两个任务的执行时间是相同的,并且只有两个线程
ScheduledThreadPool代码示例
注意,ScheduledThreadPool也可以调用execute方法,当然如果调用这个方法就和前面介绍的FixedThreadPool没什么区别了
public class ThreadPool_ScheduledDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(2);
System.out.println("线程池任务schedule时间:" + System.currentTimeMillis());
ses.schedule(new MyTask("任务1"), 2, TimeUnit.SECONDS);
ses.scheduleAtFixedRate(new MyTask("任务2"), 2, 3, TimeUnit.SECONDS);
ses.scheduleWithFixedDelay(new MyTask("任务3"), 1, 3, TimeUnit.SECONDS);
}
}
可以看到上述程序有3种执行任务的方法,下面将一一介绍
ses.schedule(new MyTask("任务1"), 2, TimeUnit.SECONDS);
运行结果
线程池任务schedule时间:1507876230078
任务1执行时间:1507876232081 线程名:pool-1-thread-1
可以看出任务1在延迟了2秒后才去执行任务
ses.scheduleAtFixedRate(new MyTask("任务2"), 2, 3, TimeUnit.SECONDS);
运行结果
线程池任务schedule时间:1507876503620
任务2执行时间:1507876505627 线程名:pool-1-thread-1
任务2执行时间:1507876508622 线程名:pool-1-thread-1
任务2执行时间:1507876511627 线程名:pool-1-thread-1
任务2执行时间:1507876514627 线程名:pool-1-thread-1
任务2执行时间:1507876517627 线程名:pool-1-thread-1
任务2执行时间:1507876520627 线程名:pool-1-thread-1
任务2执行时间:1507876523627 线程名:pool-1-thread-1
任务2执行时间:1507876526627 线程名:pool-1-thread-1
可以看出在延迟2秒后,任务2在周期性的执行着,从时间上看这个周期刚好是3秒
ses.scheduleWithFixedDelay(new MyTask("任务3"), 1, 3, TimeUnit.SECONDS);
运行结果
线程池任务schedule时间:1507876760966
任务3执行时间:1507876761969 线程名:pool-1-thread-1
任务3执行时间:1507876766972 线程名:pool-1-thread-1
任务3执行时间:1507876771974 线程名:pool-1-thread-1
任务3执行时间:1507876776979 线程名:pool-1-thread-1
任务3执行时间:1507876781985 线程名:pool-1-thread-1
任务3执行时间:1507876786990 线程名:pool-1-thread-1
任务3执行时间:1507876792000 线程名:pool-1-thread-1
可以看出在延迟1秒后,任务2在周期性的执行着,从时间上看这个周期刚好是5秒,为什么是5秒了,因为任务的执行时间为2秒,在加上设置的3秒,总共就是5秒了
线程池的关闭
线程池的有两个关闭方法,shutdown和shutdownNow
shutdown的作用
关闭线程池,不再接收新的线程,但线程池中已有的线程任务会执行完毕
shutdownNow
关闭线程池,不再接受线的线程,且线程池中已经有的线程的任务也会被立刻终止
demo示例
public class ThreadPool_ScheduledDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(2);
System.out.println("线程池任务schedule时间:" + System.currentTimeMillis());
// ses.schedule(new MyTask("任务1"), 2, TimeUnit.SECONDS);
// ses.scheduleAtFixedRate(new MyTask("任务2"), 2, 3, TimeUnit.SECONDS);
//ses.scheduleWithFixedDelay(new MyTask("任务3"), 1, 3, TimeUnit.SECONDS);
ses.scheduleWithFixedDelay(new MyTask1("任务4"), 1, 3, TimeUnit.SECONDS);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ses.shutdown(); //关闭线程池,不在接收新的线程,但线程池中已有的线程任务会执行完毕
// ses.shutdownNow(); // 关闭线程池,不再接受线的线程,且线程池中已经有的线程的任务也会被立刻终止
}
}
class MyTask1 implements Runnable {
private String taskName;
public MyTask1(String taskName) {
this.taskName = taskName;
}
public void run() {
System.out.println(taskName + "开始时间:" + System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println(taskName+"被中断");
}
System.out.println(taskName + "结束时间:" + System.currentTimeMillis());
}
}
调用shutdown方法
运行结果
线程池任务schedule时间:1507877541748
任务4开始时间:1507877542756
任务4结束时间:1507877545757
调用shutdownNow方法
运行结果
线程池任务schedule时间:1507877802636
任务4开始时间:1507877803642
任务4被中断
任务4结束时间:1507877804641
结果分析
本来是周期性的任务,调用关闭方法后,线程池中的任务就不会在执行了
任务4,在延迟1秒后开始执行
任务内部执行时间为3秒
主线程会在延迟2秒后关闭线程池
shutdown方法,结束时间-开始时间==3秒,说明的确是在任务执行完毕后才关闭的
shutdownNow方法,结束时间-开始时间==1秒,说明任务还没结束就已经被中断了
结论,推荐使用shutdown