前言
多线程软件设计可以最大限度地发挥现代多核处理器地计算能力,提高生产系统地吞吐量和性能。但是,若不加控制而是随意使用线程,系统的性能就会大打折扣。
那么,随意使用线程会存在什么问题呢?
真实生产环境下,可能会开启很多线程以支撑应用,而线程数量过大时,反而会耗尽CPU
和内存资源
。
- 虽然与进程相比,线程比较轻量级,但
创建和关闭线程需要花费时间
。如果每个小任务都创建一个线程,可能创建和销毁线程的时间比任务工作的时间还长,得不偿失。 - 线程本身需要占用内存资源,大量的线程会
抢占内存资源
,处理不当会导致out of memery,大量的线程回收会给GC带来很大的压力,延长GC的停顿时间
。
因此,我们要使用线程池,控制线程的创建和销毁。
什么是线程池
为了避免系统频繁地创建和销毁线程,我们要对创建的线程进行复用。在线程池中,当你需要使用线程时,从线程池中随便拿一个空闲线程,完成工作后,线程不会被销毁,而是归还到线程池中以被其他人使用。通过这种方式,可以节约不少创建和销毁线程对象的时间。
使用线程池后,我们以往所理解的创建线程就是从线程池中获得空闲线程,处理完线程任务后的关闭线程任务就是往线程池归还线程。
JDK对线程池的支持
为了能够更好地控制多线程,JDK提供了一套Executor框架,帮助开发人员控制线程,其本质就是一个线程池。
Executor框架结构图
以上成员均来自J.U.C包中,是JDK并发包地核心类。ThreadPoolExecutor表示一个线程池。其次,还提供了一个Executors类,扮演着线程工厂地角色,通过Executors可以获得一个拥有特定功能的线程池。
Executors提供了以下获得各种类型线程池的工厂方法:
public static ExecutorService newFixedThreadPool(int var0)
public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int var0)
以上工厂方法会返回具有不同工作特性的线程池,
-
newFixedThreadPool()
方法:该方法返回一个固定线程数量的线程池,线程的数量不会改变。当一个新的任务提交,若池中有空闲线程,则立即执行,否则新的任务会被暂存至一个任务队列中,待有空闲任务则处理任务队列中的任务。 -
newSingleThreadExecutor()
方法:该方法返回一个只有一个线程的线程池。若多于一个线程被提交,任务会被加入到任务队列,待线程空闲按照FIFO的顺序执行。 -
newCachedThreadPool()
方法:该方法返回一个可以根据实际情况调整线程数量的线程池。线程池中的线程不是固定的,任务提交时,如果线程池中有空闲线程,则使用空闲线程执行线程任务,否则创建新的线程。 -
newSingleThreadScheduledExecutor()
方法:该方法返回一个ScheduledExecutorService对象,线程池大小为1.ScheduledExecutorService接口在ExecutorService接口上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。 -
newScheduledThreadPool(int var0)
方法:该方法返回一个ScheduledExecutorService对象,可以指定线程数量。
固定大小的线程池示例
/**
* @Time : 2019/04/17 下午 04:19
* @Author : xiuc_shi
**/
public class ThreadPoolDemo {
public static class MyTask implements Runnable{
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":Thread ID:" +
Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyTask task = new MyTask();//创建一个线程任务
ExecutorService es = Executors.newFixedThreadPool(5);//创建一个线程池
for(int i = 0;i < 10;i++){
es.submit(task);//提交线程
}
es.shutdown();
}
}
>结果
1555489886561:Thread ID:9
1555489886561:Thread ID:10
1555489886562:Thread ID:11
1555489886562:Thread ID:12
1555489886562:Thread ID:13
--------------------------前五个任务和后五个任务相差1000ms
1555489887561:Thread ID:10
1555489887561:Thread ID:9
1555489887562:Thread ID:12
1555489887562:Thread ID:11
1555489887562:Thread ID:13
计划任务
newScheduledThreadPool()方法返回一个ScheduledExecutorService对象,可以根据时间需要对线程进行调度。
主要方法
ScheduledFuture<?> schedule(Runnable var1, long var2, TimeUnit var4);
ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, long var2,
long var4, TimeUnit var6);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, long var2,
long var4, TimeUnit var6);
ScheduledExecutorService不像其他线程池,它不一定会立即安排执行任务,而是起到计划执行的作用。它会在指定时间,对任务进行调度。
-
schedule()
会在给定时间对任务进行一次调度。 -
scheduleAtFixedRate()
和scheduleWithFixedDelay()
会对任务进行周期性的调度。
1. FixedRate:从上一个任务的开始执行时间开始计时,经过var4时间调度下一个任务。
2. FixedDelay:从上一个任务结束开始计时,经过var4时间,调度下一个任务。
scheduleAtFixedRate()调度示例
这个任务的执行时间为1秒钟,调度周期为2秒。
/**
* @Time : 2019/04/17 下午 05:07
* @Author : xiuc_shi
**/
public class ScheduleAtFixedRateDemo {
public static class MyTask implements Runnable{
@Override
public void run() {
System.out.println(System.currentTimeMillis()/1000 +
":Thread ID:" + Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(10);
ses.scheduleAtFixedRate(new MyTask(),0,2, TimeUnit.SECONDS);
}
}
>结果,每隔2秒进行一次调度
1555492192:Thread ID:9
1555492194:Thread ID:9
1555492196:Thread ID:11
1555492198:Thread ID:9
1555492200:Thread ID:12
1555492202:Thread ID:11
如果任务的执行时间大于调度时间会怎样呢?我们设置Thread.sleep(8000);
>结果,结果是上一个任务执行完后马上调度下一个任务
1555493505:Thread ID:9
1555493513:Thread ID:9
1555493521:Thread ID:11
1555493529:Thread ID:9
注:调度程序不保证任务会无限期地持续调用,如果任务本身抛出异常,则后续的任务都会被中断,因此要妥善做好异常处理。
提交线程
public Future<?> submit(Runnable var1) {
return this.e.submit(var1);
}
es.submit(线程任务);
public void execute(Runnable var1) {
this.e.execute(var1);
}
es.execute(线程任务);
execute()没有返回值,如果不需要线程结果可使用execute方法,性能好;
submit()返回一个Future对象,如果需要得到线程结果则使用submit方法提交,且能在主线程中通过Future对象的get方法获得线程中的异常。
关闭线程池
public void shutdown() {
this.e.shutdown();
}
es.shutdown();
不再接受新的任务,等待之前提交的任务执行完再关闭线程池。
public List<Runnable> shutdownNow() {
return this.e.shutdownNow();
}
es.shutdownNow();
不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程list集合。
网友评论