JAVA 线程池

本文探讨了在并发请求处理中如何利用线程池优化效率,特别是在请求频率高但每次请求处理时间短的情况下。通过引入线程池的概念、接口设计与简单实现,阐述了如何在服务端合理分配线程资源,减少线程创建开销,提高系统响应速度与资源利用率。
  • 用途及用法

 

 

网络请求通常有两种形式:第一种,请求不是很频繁,而且每次连接后会保持相当一段时间来读数据或者写数据,最后断开,如文件下载,网络流媒体等。另一种形式是请求频繁,但是连接上以后读/写很少量的数据就断开连接。考虑到服务的并发问题,如果每个请求来到以后服务都为它启动一个线程,那么这对服务的资源可能会造成很大的浪费,特别是第二种情况。因为通常情况下,创建线程是需要一定的耗时的,设这个时间为T1,而连接后读/写服务的时间为T2,当T1>>T2时,我们就应当考虑一种策略或者机制来控制,使得服务对于第二种请求方式也能在较低的功耗下完成。

通常,我们可以用线程池来解决这个问题,首先,在服务启动的时候,我们可以启动好几个线程,并用一个容器(如线程池)来管理这些线程。当请求到来时,可以从池中去一个线程出来,执行任务(通常是对请求的响应),当任务结束后,再将这个线程放入池中备用;如果请求到来而池中没有空闲的线程,该请求需要排队等候。最后,当服务关闭时销毁该池即可。

  • 结构

线程池中通常由这样几个概念(接口)组成:

  1. 线程池(Thread pool ),池是一个容器,容器中有很多个执行器,每一个执行器是一个线程。当然,这个容器的实现,可以是链表,可以是数组等等,不需要关心,需要关心的是,池必须提供一个可以从中取出执行器 的方法,可能还需要一个池中现有活动线程数方法,销毁池的方法等。
  2. 执行器(Executor ),每个执行器是一个线程,每个执行器可以执行一个任务 ,任务是做什么,此时还不很明确,它需要提供任务的setter/getter方法,并且作为一个线程,他可以独立运行,执行器执行完自身后,需要将自身放入池中。
  3. 任务(Task ),任务是每个线程具体要做的事,如资源下载,播放flash片段,打印一段文字到控制台等等,它本身不能执行,而需要将自身交给执行器。

整个池的机制和结构就是这样,当然,需要一个调度者(scheduler)来协调主线程和池的关系。结构,或者接口的目的是为了让我们从细节中解脱出来,从一个比较抽象的层次来描述系统,这样的好处是简单,而且设计出来的框架比较通用,可以适应很多相近相似的情况。由于Task具体干什么我们不知道,所以它几乎可以干任何适应于上边总结的网络连接的第二种情况(T1>>T2)。

  • 类的结构图

虽然为一个简单的实现设计一个标准的UML视图是不太现实的,但是这是一种受鼓励的做法,至少应该用铅笔在草纸上画出相关的视图,这样可以帮助以后的维护和更高级的扩展。

frame

  • 线程池的简单实现

实现可以是通过多种语言的,我们在此选择面向对象的JAVA,而如果你使用C的话,也没有问题,问题在上一小节已经描述清楚,语言是不重要的。

池是一个容器,我们考虑使用java.util.LinkedList类(可能由于它的长度是可变的,而且不需要我们使用者来考虑),也就是说,池需要维护一个链表。

 

Java代码 复制代码
  1. public interface Pool {//池接口   
  2.     Executor getExecutor();   
  3.     void destroy();   
  4. }  
  1. public interface Pool {//池接口  
  2.     Executor getExecutor();  
  3.     void destroy();  
  4. }  
 

 

Java代码 复制代码
  1. public interface Executor {//执行器接口   
  2.     void setTask(Task task);   
  3.     Task getTask();   
  4.     void startTask();   
  5. }  
  1. public interface Executor {//执行器接口  
  2.     void setTask(Task task);  
  3.     Task getTask();  
  4.     void startTask();  
  5. }  
 

 

鉴于执行器是池中的对象,而且外部没有必要知道其细节,我们考虑将Executor接口的实现做为Pool接口的实现的内部类。这样做的另一个好处是,更便于池的管理。

 

Java代码 复制代码
  1. import java.util.LinkedList;   
  2. import java.util.Properties;   
  3.   
  4. import redesigned.utils.PropReader;   
  5.   
  6. public class ThreadPool implements Pool{   
  7.     private boolean isShut;   
  8.     private LinkedList pool;   
  9.     private static Properties prop = PropReader.getProperties("webconfig.properties");   
  10.     private int size = Integer.parseInt(prop.getProperty("threadsperpage""3"));   
  11.     public ThreadPool(){   
  12.         // read configuration and set the   
  13.         // content of pool by objects of Executor   
  14.         isShut = false;//set the status of pool to active   
  15.         pool = new LinkedList();   
  16.         for(int i = 0; i < size; i++){   
  17.             Executor executor = new ExecutorImpl();//new a executor thread   
  18.             pool.add(executor);//add it to pool   
  19.             ((ExecutorImpl)executor).start();//start it   
  20.         }   
  21.     }   
  22.     public void destroy() {//Destroy   
  23.         synchronized(pool){   
  24.             isShut = true;//set the status of pool to inactive   
  25.             pool.notifyAll();//notify all listener.   
  26.             pool.clear();//clear the list of threads   
  27.         }   
  28.     }   
  29.   
  30.     public Executor getExecutor(){   
  31.         Executor ret = null;   
  32.         synchronized(pool){//return if any.   
  33.             if(pool.size() > 0){   
  34.                 ret = (Executor)pool.removeFirst();   
  35.             }else{   
  36.                 try {   
  37.                     pool.wait();   
  38.                 } catch (InterruptedException e) {   
  39.                     e.printStackTrace();   
  40.                 }   
  41.                 ret = (Executor)pool.removeFirst();   
  42.             }   
  43.         }   
  44.         return ret;   
  45.     }   
  46.   
  47. Executor接口的实现作为ThreadPool的内部类   
  48.     private class ExecutorImpl extends Thread implements Executor{   
  49.         private Task task;   
  50.         private Object lock = new Object();   
  51.         //private boolean loop = true;   
  52.         public ExecutorImpl(){}   
  53.         public Task getTask() {   
  54.             return this.task;   
  55.         }   
  56.   
  57.         public void setTask(Task task) {   
  58.             this.task = task;   
  59.         }   
  60.         public void startTask(){   
  61.             //System.out.println("start here");   
  62.             synchronized(lock){   
  63.                 lock.notify();   
  64.             }   
  65.         }   
  66.         public void run(){   
  67.             //get a task if any   
  68.             //then run it   
  69.             //then put self to pool   
  70.             while(!isShut){   
  71.                 synchronized(lock){   
  72.                     try {   
  73.                         lock.wait();//wait for resource   
  74.                     } catch (InterruptedException e) {   
  75.                         e.printStackTrace();   
  76.                     }   
  77.                 }   
  78.                 getTask().execute();//execute the task   
  79.                 synchronized(pool){//put it self to the pool when finish the task   
  80.                     pool.addFirst(ExecutorImpl.this);   
  81.                     pool.notifyAll();   
  82.                 }   
  83.             }   
  84.         }   
  85.     }   
  86. }   
  1. import java.util.LinkedList;  
  2. import java.util.Properties;  
  3.   
  4. import redesigned.utils.PropReader;  
  5.   
  6. public class ThreadPool implements Pool{  
  7.     private boolean isShut;  
  8.     private LinkedList pool;  
  9.     private static Properties prop = PropReader.getProperties("webconfig.properties");  
  10.     private int size = Integer.parseInt(prop.getProperty("threadsperpage""3"));  
  11.     public ThreadPool(){  
  12.         // read configuration and set the  
  13.         // content of pool by objects of Executor  
  14.         isShut = false;//set the status of pool to active  
  15.         pool = new LinkedList();  
  16.         for(int i = 0; i < size; i++){  
  17.             Executor executor = new ExecutorImpl();//new a executor thread  
  18.             pool.add(executor);//add it to pool  
  19.             ((ExecutorImpl)executor).start();//start it  
  20.         }  
  21.     }  
  22.     public void destroy() {//Destroy  
  23.         synchronized(pool){  
  24.             isShut = true;//set the status of pool to inactive  
  25.             pool.notifyAll();//notify all listener.  
  26.             pool.clear();//clear the list of threads  
  27.         }  
  28.     }  
  29.   
  30.     public Executor getExecutor(){  
  31.         Executor ret = null;  
  32.         synchronized(pool){//return if any.  
  33.             if(pool.size() > 0){  
  34.                 ret = (Executor)pool.removeFirst();  
  35.             }else{  
  36.                 try {  
  37.                     pool.wait();  
  38.                 } catch (InterruptedException e) {  
  39.                     e.printStackTrace();  
  40.                 }  
  41.                 ret = (Executor)pool.removeFirst();  
  42.             }  
  43.         }  
  44.         return ret;  
  45.     }  
  46.   
  47. Executor接口的实现作为ThreadPool的内部类  
  48.     private class ExecutorImpl extends Thread implements Executor{  
  49.         private Task task;  
  50.         private Object lock = new Object();  
  51.         //private boolean loop = true;  
  52.         public ExecutorImpl(){}  
  53.         public Task getTask() {  
  54.             return this.task;  
  55.         }  
  56.   
  57.         public void setTask(Task task) {  
  58.             this.task = task;  
  59.         }  
  60.         public void startTask(){  
  61.             //System.out.println("start here");  
  62.             synchronized(lock){  
  63.                 lock.notify();  
  64.             }  
  65.         }  
  66.         public void run(){  
  67.             //get a task if any  
  68.             //then run it  
  69.             //then put self to pool  
  70.             while(!isShut){  
  71.                 synchronized(lock){  
  72.                     try {  
  73.                         lock.wait();//wait for resource  
  74.                     } catch (InterruptedException e) {  
  75.                         e.printStackTrace();  
  76.                     }  
  77.                 }  
  78.                 getTask().execute();//execute the task  
  79.                 synchronized(pool){//put it self to the pool when finish the task  
  80.                     pool.addFirst(ExecutorImpl.this);  
  81.                     pool.notifyAll();  
  82.                 }  
  83.             }  
  84.         }  
  85.     }  
  86. }   

 

 

好了,池设计好了,再来看看任务(Task)的接口和实现

Java代码 复制代码
  1. public interface Task {//这个接口也比较简单,可以执行,可以取到执行结果   
  2.     void execute();   
  3.     byte[] getResult();   
  4. }  
  1. public interface Task {//这个接口也比较简单,可以执行,可以取到执行结果  
  2.     void execute();  
  3.     byte[] getResult();  
  4. }  
 

 

Task的实现可以是多种多样的,下边的例子是一个加载资源的Task.使用方式

 

 

Java代码 复制代码
  1. Pool pool = new ThreadPool();// new a ThreadPool   
  2. //load resources on each page, and start #s of thread.   
  3. for(int i = 0; i < resourceList.size();i++){   
  4.     Executor executor = pool.getExecutor(); // get Executor form pool   
  5.     Task resourceLoader = new ResourceLoader((String)resourceList.get(i));   
  6.     executor.setTask(resourceLoader); // set the task to executor   
  7.     executor.startTask(); // try to start the executor.   
  8. }   
  9.   
  10. //wait while all task are done, the destroy the pool.   
  11. pool.destroy();   
  1. Pool pool = new ThreadPool();// new a ThreadPool  
  2. //load resources on each page, and start #s of thread.  
  3. for(int i = 0; i < resourceList.size();i++){  
  4.     Executor executor = pool.getExecutor(); // get Executor form pool  
  5.     Task resourceLoader = new ResourceLoader((String)resourceList.get(i));  
  6.     executor.setTask(resourceLoader); // set the task to executor  
  7.     executor.startTask(); // try to start the executor.  
  8. }  
  9.   
  10. //wait while all task are done, the destroy the pool.  
  11. pool.destroy();   

 

  • 优势,或者适用范围
  1. 在并发时,需要被并发的线程不需要知道自己什么时候需要被启动,它子需要考虑这样一种情况:它自己从一个地方取出来一个执行器,然后把任务交给执行器,然后等待执行器结束即可,他关心的是自己所需要干的事,或者自己负责的事。这样,大家都简单,因为只需要做好自己的事情就好了。面向对象的一个秘诀为:永远相信合作者,使用别人的接口而不是自己去实现所有的接口。
  2. 这种T1>>T2的请求方式在网络中固然是常见的,在实际问题中同样是常见的。因此,掌握这种模式可能会对我们以后的程序设计提供方便和好处。
  • 小结

同步问题: 同步在线程的并发中意义非常之大,对临界资源的控制是并发时最关键的地方。如在线程池中,当池中没有空闲的线程时,新来的请求就必须等待,而一旦一个Task运行结束后,一方面将自己放入池中,一方面需要通知等待在pool中的其他线程。每一个执行器线程,一开始启动,则进入等待状态,此时不会消耗CPU资源。而当在外部调用执行器的startTask()方法,即可通知线程从等待状态中醒来,去出Task,执行之,将执行器本身放入池中,然后继续等待。

 

当然,实现的策略是可以多种多样的,但是问题的本质已经在第二小节结构 很明确的被定义了。

 

最近开始学习JAVA,同时也开始熟悉面向对象的思维方式,这篇日志,一来作为一个备忘,二来可以对可能需要的人提供帮助。以上可以算作是小结。 

<think>我们正在讨论Java线程池的使用和原理。根据引用内容,线程池Java中主要通过ThreadPoolExecutor类实现,从JDK5开始内置支持。线程池的好处包括重用线程、控制并发数量、管理线程生命周期等。常见的线程池类型有:FixedThreadPool(固定容量)、CachedThreadPool(缓存)、SingleThreadExecutor(单线程)、ScheduledThreadPool(定时任务)以及通过ThreadPoolExecutor自定义创建。我们将分步骤讲解:1.线程池的核心原理2.创建线程池的五种方式(包括不推荐的原因)3.重点讲解ThreadPoolExecutor及其参数4.线程池的工作机制(任务提交、执行、拒绝策略等)5.使用示例注意:根据引用[3]和[4],ThreadPoolExecutor是构建线程池的核心类,因此我们将重点分析。步骤1:线程池的核心原理线程池通过预先创建一定数量的线程并维护它们,避免频繁创建和销毁线程的开销。当有任务提交时,线程池分配空闲线程执行任务;若没有空闲线程,任务将被放入队列等待;当队列满时,根据设定的拒绝策略处理。步骤2:创建线程池的五种方式(引用[3]和[5])1.newCachedThreadPool:创建一个可缓存的线程池,线程数可无限增加(最大为Integer.MAX_VALUE),空闲线程默认60秒回收。不推荐原因:可能创建过多线程导致OOM。2.newFixedThreadPool:固定大小的线程池,使用无界队列(LinkedBlockingQueue)。不推荐原因:无界队列可能堆积大量任务导致OOM。3.newSingleThreadExecutor:只有一个线程的线程池,同样使用无界队列。不推荐原因同上。4.newScheduledThreadPool:用于定时或周期性任务的线程池。不推荐直接使用的原因是其最大线程数为Integer.MAX_VALUE,可能创建过多线程。5.推荐方式:直接使用ThreadPoolExecutor构造函数创建,可明确指定参数,避免资源耗尽。步骤3:ThreadPoolExecutor的七个参数(引用[3]和[4])构造方法如下:```javapublicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueue<Runnable>workQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)```1.corePoolSize:核心线程数,即使空闲也不会被回收(除非设置allowCoreThreadTimeOut)2.maximumPoolSize:最大线程数3.keepAliveTime:非核心线程的空闲存活时间4.unit:存活时间单位5.workQueue:任务队列(常用有ArrayBlockingQueue有界队列,LinkedBlockingQueue无界队列,SynchronousQueue同步移交队列)6.threadFactory:线程工厂,用于创建线程7.handler:拒绝策略(当线程池和队列都满时触发)步骤4:线程池的工作机制(任务提交执行流程)1.提交任务后,如果当前线程数小于corePoolSize,则创建新线程执行(即使有空闲线程)。2.如果当前线程数>=corePoolSize,则将任务放入workQueue。3.如果队列已满,且线程数小于maximumPoolSize,则创建新的非核心线程执行。4.如果队列已满且线程数达到maximumPoolSize,则触发拒绝策略。拒绝策略(handler)有四种:-AbortPolicy(默认):抛出RejectedExecutionException-CallerRunsPolicy:由调用线程执行该任务-DiscardPolicy:丢弃任务,不抛异常-DiscardOldestPolicy:丢弃队列最前面的任务,然后重试步骤5:使用示例(以ThreadPoolExecutor为例)下面是一个自定义线程池的示例:```javaimportjava.util.concurrent.*;publicclassThreadPoolExample{publicstaticvoidmain(String[]args){//创建线程池ThreadPoolExecutorexecutor=newThreadPoolExecutor(2,//核心线程数5,//最大线程数60,//空闲线程存活时间TimeUnit.SECONDS,newArrayBlockingQueue<>(10),//有界队列,容量10Executors.defaultThreadFactory(),newThreadPoolExecutor.AbortPolicy());//拒绝策略:直接抛出异常//提交任务for(inti=0;i<15;i++){finalinttaskId=i;executor.execute(()->{System.out.println("Task"+taskId+"isrunningonthread"+Thread.currentThread().getName());try{Thread.sleep(1000);//模拟任务执行}catch(InterruptedExceptione){e.printStackTrace();}});}//关闭线程池executor.shutdown();}}```注意:实际任务数15个,核心线程2个,最大线程5个,队列容量10个。因此前2个任务会创建核心线程执行,接下来的10个任务放入队列,然后第13、14、15个任务会创建非核心线程(共3个非核心线程,总线程数5个)。如果超过15个任务(比如16个),则会触发拒绝策略。最后,关于线程池的关闭,可以使用shutdown()(等待已提交任务完成)或shutdownNow()(尝试中断所有线程,停止任务)。总结:使用线程池时,推荐直接使用ThreadPoolExecutor构造函数,根据实际需求设置参数,避免使用Executors工厂方法可能导致的资源耗尽问题。相关问题:1.线程池中任务队列有哪些类型?各自特点是什么?2.如何合理设置线程池的核心线程数和最大线程数?3.线程池的拒绝策略有哪些?如何自定义拒绝策略?4.如何监控线程池的运行状态?5.线程池中线程是如何创建的?如何自定义线程工厂?</think>### Java线程池使用指南与原理分析 Java线程池通过`java.util.concurrent`包实现,核心类是`ThreadPoolExecutor`。线程池的核心思想是**复用线程**,避免频繁创建销毁线程的开销。线程池中的线程执行完任务后不会销毁,而是回到池中等待新任务[^2]。 --- #### **一、线程池的创建方式** Java提供5种创建线程池的方法(实际都是`ThreadPoolExecutor`的包装): 1. **固定大小线程池**(推荐) ```java ExecutorService fixedPool = Executors.newFixedThreadPool(5); // 固定5个线程 ``` - 特点:核心线程数=最大线程数,使用无界队列`LinkedBlockingQueue` - 风险:任务过多时可能导致OOM(队列无限增长)[^5] 2. **缓存线程池**(不推荐) ```java ExecutorService cachedPool = Executors.newCachedThreadPool(); ``` - 特点:线程数无上限(`Integer.MAX_VALUE`),空闲线程60秒回收 - 风险:高并发时可能创建大量线程导致OOM[^3] 3. **单线程池** ```java ExecutorService singlePool = Executors.newSingleThreadExecutor(); ``` - 特点:仅1个线程,保证任务顺序执行 - 风险:同固定线程池(无界队列问题) 4. **定时任务线程池** ```java ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3); ``` 5. **推荐做法:手动创建`ThreadPoolExecutor`** ```java ThreadPoolExecutor customPool = new ThreadPoolExecutor( 4, // 核心线程数 10, // 最大线程数 60, // 空闲线程存活时间(秒) TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), // 有界队列(容量100) new ThreadPoolExecutor.AbortPolicy() // 拒绝策略 ); ``` --- #### **二、核心参数详解(7个关键参数)** ```java public ThreadPoolExecutor( int corePoolSize, // 核心线程数(常驻线程) int maximumPoolSize, // 最大线程数 long keepAliveTime, // 非核心线程空闲存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 ) ``` 1. **任务队列类型** - `ArrayBlockingQueue`:**有界队列**(推荐),避免OOM - `LinkedBlockingQueue`:**无界队列**(默认) - `SynchronousQueue`:不存储任务,直接移交线程 2. **拒绝策略**(当队列和线程池全满时触发) - `AbortPolicy`(默认):抛出`RejectedExecutionException` - `CallerRunsPolicy`:由调用者线程直接执行任务 - `DiscardPolicy`:静默丢弃任务 - `DiscardOldestPolicy`:丢弃队列最旧的任务 --- #### **三、线程池工作原理** 1. **任务提交流程**: - 当线程数 < `corePoolSize`,**创建新线程**执行任务 - 当线程数 >= `corePoolSize`,任务进入**工作队列** - 当队列已满且线程数 < `maximumPoolSize`,**创建临时线程**执行 - 当队列满且线程数达到`maximumPoolSize`,触发**拒绝策略**[^4] 2. **线程回收机制**: - 核心线程默认不回收(除非设置`allowCoreThreadTimeOut(true)`) - 非核心线程空闲超过`keepAliveTime`后自动销毁 --- #### **四、最佳实践** 1. **参数设置建议**: - CPU密集型任务:核心线程数 = CPU核数 + 1 - IO密集型任务:核心线程数 = 2 * CPU核数 - 使用**有界队列**防止资源耗尽 - 拒绝策略选择`CallerRunsPolicy`保证任务不丢失 2. **代码示例**: ```java ThreadPoolExecutor executor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), // 核心线程数=CPU核数 Runtime.getRuntime().availableProcessors() * 2, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ); // 提交任务 executor.execute(() -> System.out.println("Task running")); // 优雅关闭 executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); ``` 3. **注意事项**: - 避免使用`Executors`快捷方法(有OOM风险) - 监控线程池状态(通过`getActiveCount()`等方法) - 务必调用`shutdown()`释放资源 --- #### **五、原理解析** - **线程复用**:通过`Worker`类封装线程,循环从队列获取任务执行[^3] - **任务调度**:核心逻辑在`ThreadPoolExecutor.execute()`方法中 - **资源控制**:通过`BlockingQueue`实现生产者-消费者模型 > 线程池的本质是**资源池化技术**(Pooling),通过复用线程降低系统开销,同时提供流量缓冲和容错能力[^4]。 --- ### 相关问题 1. **为什么`FixedThreadPool`使用无界队列会导致OOM?如何避免?** 2. **线程池中`corePoolSize`和`maximumPoolSize`设置不当会产生什么后果?** 3. **如何实现一个自定义拒绝策略(如记录日志或降级处理)?** 4. **线程池的`shutdown()`和`shutdownNow()`有什么区别?** 5. **如何监控线程池的运行状态(如队列积压、活跃线程数)?** [^1]: ThreadPoolExecutor是Java线程池的核心实现类。 [^2]: JDK5开始内置线程池,线程执行后返回池中复用。 [^3]: 手动创建ThreadPoolExecutor可避免资源耗尽风险。 [^4]: 线程池通过队列缓冲和动态扩缩容管理任务。 [^5]: FixedThreadPool的无界队列可能导致OOM。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值