Java多线程技术研究(三)-线程池

左手的ㄟ右手 2022-06-14 08:25 323阅读 0赞

在多线程开发中,当线程数量较多时,并且每个线程的执行时间较短,因而需要频繁的创建线程和销毁线程,这样会大大较低系统的吞吐能力。这时就可以采用线程技术,实现线程执行完成后不会被销毁,可以被反复使用。假设一个服务器完成一项任务所需要的时间为T1,创建一个线程的时间为T2,销毁一个线程的时间为T3。当((T2+T3))/T1 的值较大时,才用线程池的技术就可以很好的提高服务器性能。

线程池顾名思义指的就是线程的池子,初始时,我们创建一定数量的线程放置在线程池,每当一个新的任务提交时,我们就可以从池子中取出一个空闲的线程去执行该任务,执行完后才能后,线程不会被销毁,被重现放回线程池的空闲队列中。

线程池技术正是关注如何缩短或调整线程的创建时间或销毁线程时间的技术。我们正常情况下,会在启动应用程序时,创建线程池,而退出应用时,销毁线程池,因而在应用处理请求过程中只有执行服务的时间开销。注意:需要根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下。

1、在Java类库中提供了线程池的机制,常见的以下几种线程池:
1) newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行,类似于Timer

  1. package com.wygu.thread.study;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. public class MultiThreadPool {
  5. public static void main(String []argv){
  6. TaskRunnable taskRunnableA = new TaskRunnable("ThreadA");
  7. TaskRunnable taskRunnableB = new TaskRunnable("ThreadB");
  8. ExecutorService executorService = Executors.newScheduledThreadPool(2);
  9. executorService.execute(taskRunnableA);
  10. executorService.execute(taskRunnableB);
  11. }
  12. }
  13. class TaskRunnable implements Runnable{
  14. private String threadNm;
  15. public TaskRunnable(String threadNm) {
  16. this.threadNm = threadNm;
  17. }
  18. @Override
  19. public void run() {
  20. for(int i=0;i<5;i++){
  21. System.out.println(this.threadNm+"---->"+i);
  22. try {
  23. Thread.sleep(10);
  24. } catch (InterruptedException e) {
  25. // TODO Auto-generated catch block
  26. e.printStackTrace();
  27. }
  28. }
  29. }
  30. }

程序运行结果为:
ThreadB—->0
ThreadA—->0
ThreadA—->1
ThreadB—->1
ThreadA—->2
ThreadB—->2
ThreadA—->3
ThreadB—->3
ThreadA—->4
ThreadB—->4
2) newCachedThreadPool
创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空闲的线程;当需求增加时,会增加工作线程数量;线程池规模无限制;空闲线程被保留60秒。
3) newFixedThreadPool
创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
4) newSingleThreadExecutor
线程池中任意时刻只有一个线程,队列中的任务按照顺序执行。

2、工作中的应用
在后台交易系统中,每天都会产生大量的流水日志,如果系统实现实现日志数据清理的机制功能,会导致日志表中的数据日积月累,越来越多,严重影响到系统的性能。笔者刚工作时,接触到的第一个系统就是这种情况,交易日志表中存储接近上千万的数据,严重影响了交易的响应速度。为解决这个问题,往往会创建两张交易日志表,实现按日轮换使用,利用定时任务清理即将使用的表中的记录。

首先介绍ServletContextListener机制:
(1)接口ServletContextListener监听 ServletContext对象的生命周期。
(2) 当Servlet容器启动或终止Web应用时,就会触发ServletContextEvent事件,该事件由ServletContextListener来处理。
(3) 在 ServletContextListener接口中定义了处理ServletContextEvent事件的两个方法contextInitialized(),contextDestroyed()。
首先在web.xml中配置

  1. <listener>
  2. <listener-class> com.unionpay.cloudPayment.util.TimedTaskClearUtil</listener-class>
  3. </listener>

下面创建类TimedTaskClearUtil,并继承ServletContextListener,重写方法contextInitialized(),contextDestroyed():

  1. import java.sql.Connection;
  2. import java.sql.SQLException;
  3. import java.text.ParseException;
  4. import java.text.SimpleDateFormat;
  5. import java.util.Calendar;
  6. import java.util.Date;
  7. import java.util.concurrent.ScheduledThreadPoolExecutor;
  8. import java.util.concurrent.TimeUnit;
  9. import javax.servlet.ServletContextEvent;
  10. import javax.servlet.ServletContextListener;
  11. public class TimedTaskClearUtil implements ServletContextListener{
  12. private static ScheduledThreadPoolExecutor service;
  13. public void contextInitialized(ServletContextEvent sce) {
  14. service = new ScheduledThreadPoolExecutor(2); //初始化线程池中线程数为2
  15. service.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);//当容器停止时,任务也将立刻停止
  16. service.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
  17. }
  18. public void contextDestroyed(ServletContextEvent sce) {
  19. try {
  20. if(!service.isShutdown()){
  21. service.shutdownNow();
  22. System.out.println("*****************定时任务:日志表清理销毁成功******************");
  23. }
  24. Thread.sleep(1000);
  25. } catch (InterruptedException e) {
  26. // TODO Auto-generated catch block
  27. e.printStackTrace();
  28. }
  29. }
  30. public static void execuTimedTask(){
  31. final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");// 设置日期格式
  32. String curDateStr = sdf.format(new Date());
  33. String nextTime = String.format("%s%s", curDateStr.substring(0, 8),"234000");
  34. String cycleUnit = "H"; //H表示以小时为单位,M表示以分钟为单位,S表示以秒为单位
  35. int cycleSpan = 24; //表示24 cycleUnit
  36. long secondDiff = 0;
  37. try {
  38. Date curDate = sdf.parse(curDateStr);
  39. Date nextDate= sdf.parse(nextTime);
  40. secondDiff=(nextDate.getTime()-curDate.getTime())/1000; //距离第一次任务的执行还需要多长时间
  41. } catch (ParseException e) {
  42. e.printStackTrace();
  43. }
  44. Runnable runnable = new Runnable() {
  45. public void run() {
  46. /*执行日志表的清理 **清理时建议使用truncate操作,减少磁盘碎片 */
  47. } catch (Exception e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. };
  52. service.scheduleAtFixedRate(runnable, secondDiff, revToSecond(cycleUnitcycleSpan), TimeUnit.SECONDS);//定时任务按照固定的频率执行
  53. }
  54. private static long revToSecond(String cycleUnit,int cycleSpan){
  55. long timeSpan = 0;
  56. if("H".equals(cycleUnit)){
  57. timeSpan = cycleSpan*3600;
  58. }else if("M".equals(cycleUnit)){
  59. timeSpan = cycleSpan*60;
  60. }else if("S".equals(cycleUnit)){
  61. timeSpan = cycleSpan;
  62. }
  63. return timeSpan;
  64. }
  65. }

在启动servlet中,可以直接加载定时任务,比如在InitServlet中,调用:

  1. TimedTaskClearUtil.execuTimedTask();

线程池除了可以直接使用Java Api提供的类创建外,也可以自己创建。
2、自定义线程池
一个线程池包括以下四个基本组成部分:
(1)线程池管理器(ThreadPool):用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;
(2)工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环式执行任务;
(3)任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
(4)任务队列(taskQueue):用于存放没有处理的任务,提供一种缓冲机制。

自定义线程池代码:参考博客:http://www.cnblogs.com/kinghitomi/archive/2012/01/19/2327418.html
创建线程管理器

  1. package com.wygu.thread.study;
  2. import java.util.concurrent.LinkedBlockingQueue;
  3. /** * * @author guweiyu *@category 一个完整的线程至少需要包含以下四个部分:线程池管理器(ThreadPoll),工作线程(WorkThread),任务接口(Task),任务队列(TaskQueue) */
  4. //线程池管理器
  5. public final class ThreadPoolExecutOwn {
  6. // 设置线程池中默认线程数为5
  7. private static int worker_thread_num = 5;
  8. private static int default_thread_num = 5;
  9. // 工作线程
  10. private WorkThread[] workThread;
  11. // 未处理的任务
  12. public static volatile int finished_task = 0;
  13. // 任务队列,作为一个缓冲
  14. public static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
  15. private static ThreadPoolExecutOwn threadPool;
  16. // 创建具有默认线程个数的线程池
  17. private ThreadPoolExecutOwn() {
  18. this(default_thread_num);
  19. }
  20. // 创建线程池中的线程,worker_thread_num为线程池中工作线程的个数
  21. private ThreadPoolExecutOwn(int worker_thread_num) {
  22. ThreadPoolExecutOwn.worker_thread_num = worker_thread_num;
  23. workThread = new WorkThread[worker_thread_num];
  24. for (int i = 0; i < worker_thread_num; i++) {
  25. workThread[i] = new WorkThread();
  26. workThread[i].start();
  27. }
  28. }
  29. // 获得默认值default_thread_num的线程池
  30. public static ThreadPoolExecutOwn getThreadPool() {
  31. return getThreadPool(ThreadPoolExecutOwn.default_thread_num);
  32. }
  33. // 获得一个指定线程个数的线程池,worker_num(>0)为线程池中工作线程的个数
  34. public static ThreadPoolExecutOwn getThreadPool(int worker_num) {
  35. if (worker_num <= 0)
  36. worker_num = ThreadPoolExecutOwn.default_thread_num;
  37. if (threadPool == null)
  38. threadPool = new ThreadPoolExecutOwn(worker_num);
  39. return threadPool;
  40. }
  41. // 执行任务,将任务加入到任务队列中,等待线程池管理器调用
  42. public void execute(Runnable task) {
  43. synchronized (ThreadPoolExecutOwn.taskQueue) {
  44. ThreadPoolExecutOwn.taskQueue.add(task);
  45. ThreadPoolExecutOwn.taskQueue.notify();
  46. }
  47. }
  48. // 销毁线程池,方法保证在所有任务都完成的情况下才会销毁所有线程,否则等待任务完成才销毁
  49. public void destroy() {
  50. while (!ThreadPoolExecutOwn.taskQueue.isEmpty()) {
  51. try {
  52. Thread.sleep(10);
  53. } catch (InterruptedException e) {
  54. e.printStackTrace();
  55. }
  56. }
  57. // 工作线程停止工作,且置为null
  58. for (int i = 0; i < worker_thread_num; i++) {
  59. workThread[i].stopWorker();
  60. workThread[i] = null;
  61. }
  62. threadPool=null;
  63. ThreadPoolExecutOwn.taskQueue.clear();// 清空任务队列
  64. }
  65. // 返回工作线程的个数
  66. public int getWorkThreadNumber() {
  67. return worker_thread_num;
  68. }
  69. // 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成
  70. public int getFinishedTasknumber() {
  71. return finished_task;
  72. }
  73. // 返回任务队列的长度,即还没处理的任务个数
  74. public int getWaitTasknumber() {
  75. return ThreadPoolExecutOwn.taskQueue.size();
  76. }
  77. }

创建工作线程

  1. package com.wygu.thread.study;
  2. /* * @author guweiyu */
  3. public class WorkThread extends Thread {
  4. private boolean isRunning = true; //用于判断工作线程是否有效,能否继续执行
  5. //如果任务队列为空,则工作线程等待,否则从队列中取出任务继续执行
  6. @Override
  7. public void run() {
  8. Runnable r = null;
  9. while (isRunning) {
  10. synchronized (ThreadPoolExecutOwn.taskQueue) {
  11. while (isRunning && ThreadPoolExecutOwn.taskQueue.isEmpty()) {
  12. // 队列为空
  13. try {
  14. ThreadPoolExecutOwn.taskQueue.wait(20);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. if (!ThreadPoolExecutOwn.taskQueue.isEmpty())
  20. r = ThreadPoolExecutOwn.taskQueue.remove();// 取出任务
  21. }
  22. if (r != null) {
  23. r.run();// 执行任务
  24. }
  25. ThreadPoolExecutOwn.finished_task++;
  26. r = null;
  27. }
  28. }
  29. // 停止工作,让该线程自然执行完run方法,自然结束
  30. public void stopWorker() {
  31. isRunning = false;
  32. }
  33. }

创建工作任务

  1. package com.wygu.thread.study;
  2. public class WorkTask implements Runnable{
  3. private String taskName = null;
  4. public WorkTask(String taskName) {
  5. this.taskName = taskName;
  6. }
  7. @Override
  8. public void run() {
  9. System.out.println("任务开始执行:"+this.taskName);
  10. try {
  11. Thread.sleep(100);
  12. } catch (InterruptedException e) {
  13. // TODO Auto-generated catch block
  14. e.printStackTrace();
  15. }
  16. System.out.println("任务完成:"+this.taskName);
  17. }
  18. }

Main方法调用

  1. package com.wygu.thread.study;
  2. public class Main {
  3. public static void main(String[] args) {
  4. ThreadPoolExecutOwn threadPoolExecutOwn = ThreadPoolExecutOwn.getThreadPool(3);//创建线程数为4的线程池
  5. WorkTask []workTask = new WorkTask[100];
  6. for(int i=0;i<10;i++){
  7. workTask[i] = new WorkTask("TASK"+i);
  8. threadPoolExecutOwn.execute(workTask[i]);
  9. }
  10. }
  11. }

程序运行结果为
TASK0:开始执行**
TASK2:开始执行
*
TASK1:开始执行
*
TASK0:执行结束###
TASK2:执行结束###
TASK1:执行结束###
TASK4:开始执行
*
TASK3:开始执行
*
TASK5:开始执行
*
TASK4:执行结束###
TASK5:执行结束###
TASK3:执行结束###
TASK6:开始执行
*
TASK8:开始执行
*
TASK7:开始执行
*
TASK6:执行结束###
TASK8:执行结束###
TASK9:开始执行
**
TASK7:执行结束###
TASK9:执行结束###

发表评论

表情:
评论列表 (有 0 条评论,323人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java线-线

    线程池-基本原理 概述 : ​ 提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子

    相关 Java线技术

    线程池的优点 1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。 2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗

    相关 线技术

    1、线程池技术的简单概括        对于服务端的程序,经常面对的是客户端传入的短小(执行时间短、工作内容较为单一)任务,需要服务端快速处理并返回结果。如果服务端每次接受到