服务端应用程序(如数据库和 Web 服务器)须要处理来自客户端的高并发、耗时较短的请求任务,因此频繁的建立处理这些请求的所须要的线程就是一个很是消耗资源的操做。常规的方法是针对一个新的请求建立一个新线程,虽然这种方法彷佛易于实现,但它有重大缺点。为每一个请求建立新线程将花费更多的时间,在建立和销毁线程时花费更多的系统资源。所以同时建立太多线程的 JVM 可能会致使系统内存不足,这就须要限制要建立的线程数,也就是须要使用到线程池。vue
线程池技术就是线程的重用技术,使用以前建立好的线程来执行当前任务,并提供了针对线程周期开销和资源冲突问题的解决方案。 因为请求到达时线程已经存在,所以消除了线程建立过程致使的延迟,使应用程序获得更快的响应。spring
上图表示线程池初始化具备3 个线程,任务队列中有5 个待运行的任务对象。数据库
执行器线程池方法编程
方法 | 描述 |
---|---|
newFixedThreadPool(int) | 建立具备固定的线程数的线程池,int参数表示线程池内线程的数量 |
newCachedThreadPool() | 建立一个可缓存线程池,该线程池可灵活回收空闲线程。若无空闲线程,则新建线程处理任务。 |
newSingleThreadExecutor() | 建立一个单线程化的线程池,它只会用惟一的工做线程来执行任务 |
newScheduledThreadPool | 建立一个定长线程池,支持定时及周期性任务执行 |
在固定线程池的状况下,若是执行器当前运行的全部线程,则挂起的任务将放在队列中,并在线程变为空闲时执行。后端
在下面的内容中,咱们将介绍线程池的executor执行器。缓存
建立线程池处理任务要遵循的步骤springboot
//第一步: 建立一个任务对象(实现Runnable接口),用于执行具体的任务逻辑 (Step 1) class Task implements Runnable { private String name; public Task(String s) { name = s; } // 打印任务名称并Sleep 1秒 // 整个处理流程执行5次 public void run() { try{ for (int i = 0; i<=5; i++) { if (i==0) { Date d = new Date(); SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss"); System.out.println("任务初始化" + name +" = " + ft.format(d)); //第一次执行的时候,打印每个任务的名称及初始化的时间 } else{ Date d = new Date(); SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss"); System.out.println("任务正在执行" + name +" = " + ft.format(d)); // 打印每个任务处理的执行时间 } Thread.sleep(1000); } System.out.println("任务执行完成" + name); } catch(InterruptedException e) { e.printStackTrace(); } } }
测试用例服务器
public class ThreadPoolTest { // 线程池里面最大线程数量 static final int MAX_SIZE = 3; public static void main (String[] args) { // 建立5个任务 Runnable r1 = new Task("task 1"); Runnable r2 = new Task("task 2"); Runnable r3 = new Task("task 3"); Runnable r4 = new Task("task 4"); Runnable r5 = new Task("task 5"); // 第二步:建立一个固定线程数量的线程池,线程数为MAX_SIZE ExecutorService pool = Executors.newFixedThreadPool(MAX_SIZE); // 第三步:将待执行的任务对象交给ExecutorService进行任务处理 pool.execute(r1); pool.execute(r2); pool.execute(r3); pool.execute(r4); pool.execute(r5); // 第四步:关闭线程池 pool.shutdown(); } }
示例执行结果多线程
任务初始化task 1 = 05:25:55 任务初始化task 2 = 05:25:55 任务初始化task 3 = 05:25:55 任务正在执行task 3 = 05:25:56 任务正在执行task 1 = 05:25:56 任务正在执行task 2 = 05:25:56 任务正在执行task 1 = 05:25:57 任务正在执行task 3 = 05:25:57 任务正在执行task 2 = 05:25:57 任务正在执行task 3 = 05:25:58 任务正在执行task 1 = 05:25:58 任务正在执行task 2 = 05:25:58 任务正在执行task 2 = 05:25:59 任务正在执行task 3 = 05:25:59 任务正在执行task 1 = 05:25:59 任务正在执行task 1 = 05:26:00 任务正在执行task 2 = 05:26:00 任务正在执行task 3 = 05:26:00 任务执行完成task 3 任务执行完成task 2 任务执行完成task 1 任务初始化task 5 = 05:26:01 任务初始化task 4 = 05:26:01 任务正在执行task 4 = 05:26:02 任务正在执行task 5 = 05:26:02 任务正在执行task 4 = 05:26:03 任务正在执行task 5 = 05:26:03 任务正在执行task 5 = 05:26:04 任务正在执行task 4 = 05:26:04 任务正在执行task 4 = 05:26:05 任务正在执行task 5 = 05:26:05 任务正在执行task 4 = 05:26:06 任务正在执行task 5 = 05:26:06 任务执行完成task 4 任务执行完成task 5
如程序执行结果中显示的同样,任务 4 或任务 5 仅在池中的线程变为空闲时才执行。在此以前,额外的任务将放在待执行的队列中。并发
线程池执行前三个任务,线程池内线程回收空出来以后再去处理执行任务 4 和 5
使用这种线程池方法的一个主要优势是,假如您但愿一次处理10000个请求,但不但愿建立10000个线程,从而避免形成系统资源的过量使用致使的宕机。您可使用此方法建立一个包含500个线程的线程池,而且能够向该线程池提交500个请求。
ThreadPool此时将建立最多500个线程,一次处理500个请求。在任何一个线程的进程完成以后,ThreadPool将在内部将第501个请求分配给该线程,并将继续对全部剩余的请求执行相同的操做。在系统资源比较紧张的状况下,线程池是保证程序稳定运行的一个有效的解决方案。
线程池大小优化: 线程池的最佳大小取决于可用的处理器数量和待处理任务的性质。对于CPU密集型任务,假设系统有N个逻辑处理核心,N 或 N+1 的最大线程池数量大小将实现最大效率。对于 I/O密集型任务,须要考虑请求的等待时间(W)和服务处理时间(S)的比例,线程池最大大小为 N*(1+ W/S)会实现最高效率。
不要教条的使用上面的总结,须要根据本身的应用任务处理类型进行灵活的设置与调优,其中少不了测试实验。
本文转载注明出处(必须带链接,不能只转文字):字母哥博客 - zimug.com
以为对您有帮助的话,帮我点赞、分享!您的支持是我不竭的创做动力! 。另外,笔者最近一段时间输出了以下的精品内容,期待您的关注。