java线程池

1.JDK中的Executor框架是基于生产者-消费者模式的线程池,提交任务的线程是生产者,执行任务的线程是消费者。

Executor线程池可以用于异步任务执行,而且支持很多不同类型任务执行策略,同时为任务提交和任务执行之间的解耦提供了标准方法。

Executor线程池支持如下三种线程执行策略:

(1).顺序执行:

类似于单线程顺序执行任务,优点是实现简单;缺点是扩展性受限,执行效率低下,例子代码如下:

 

  1. public class WithinThreadExecutor implements Executor{  
  2.     public void execute(Runnable r){  
  3.         r.run();  
  4.     }  
  5. }  

 

(2)每请求每线程:

为每个请求创建一个新的线程,优点是可以并行处理;缺点是线程生命周期开销大,活动线程受内存资源、JVM以及操作系统的限制,当负载过大时响应性和吞吐量会下降严重,同时还会影响稳定性,例子代码如下:

  1. public class ThreadPerTaskExecutor implements Executor{  
  2.     public void execute(Runnable r){  
  3.         new Thread(r).start();  
  4.     }  
  5. }  

(3)线程池:

使用线程池可以重用已有线程,减少线程生命周期开销,同时可以调整活动线程数量,既可以确保足够的并发性,又避免过多线程相互竞争资源,例子代码如下:

  1. public class TaskExecutionWebServer {  
  2.     private static final int NTHREADS = 100;  
  3.     private static final ExecutorService exec = Executors  
  4.             .newFixedThreadPool(NTHREADS);  
  5.   
  6.     public static void main(String[] args) throws IOException {  
  7.         ServerSocket socket = new ServerSocket(80);  
  8.         while (true) {  
  9.             final Socket connection = socket.accept();  
  10.             Runnable task = new Runnable() {  
  11.                 public void run() {  
  12.                     handleRequest(connection);  
  13.                 }  
  14.             };  
  15.             exec.execute(task);  
  16.         }  
  17.     }  
  18. }  

2.Executor常用的创建线程池静态工厂方法:

(1).newFixedThreadPool

创建一个定长的线程池,每当提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化,若一个线程由于非预期的异常而结束,线程池会补充一个新的线程。

(2).newCachedThreadPool

创建一个可缓存的线程池,若当前线程池的长度超过了处理的需要时,它可以灵活地回收空闲的线程,当需求增加时,它可以灵活地添加新的线程,而并不会对池的长度做任何限制。

(3).newSingleThreadExecutor

创建一个单线程化的executor,只创建唯一的工作者线程来执行任务,若这个线程异常结束,会有另一个取代它。Executor会保证任务依照任务队列所规定的顺序执行。

(4).newScheduledThreadPool

创建一个支持定时的以及周期性执行的任务的定长线程池。

3.Executor的生命周期:

ExecutorService接口扩展了Executor,提供了以下用于生命周期管理的方法:

  1. public interface ExecutorService extends Executor{  
  2.     void shutdown();  
  3.     list<Runnable> shutdownNow();  
  4.     boolean isShutdown();  
  5.     boolean isTerminated();  
  6.     boolean awaitTermination(long timeout, TimeUnit unit) throws InterruputedException;  
  7.     ......  
  8. }  

ExecutorService接口暗示了Executor的生命周期有以下3中状态:

(1).运行状态:

ExecutorService最初创建后的初始状态是运行状态。

(2).关闭状态:

ExecutorServicesutdown方法会启动一个平缓的关闭过程,停止接收新任务,同时等待已提交的任务执行完成(包括尚未开始执行的任务)

ExecutorServicesutdownNow方法会启动一个强制的关闭过程,尝试取消所有运行中的任务和排在队列中尚未开始执行的任务。

(3)终止状态:

一旦所有任务全部完成后,ExecutorService就会进入终止状态,通过调研ExecutorServiceawaitTermination方法等待达到终止状态,也可以调用isTerminated来轮询是否达到终止状态。

4.TimerScheduledExecutorService

JDK1.5之前,经常使用Timer(开源的Quartz框架也可以)作为定时器管理任务的延迟或周期性执行,在JDK1.5引入了ScheduledExecutorService,使用线程池作为定时器管理任务的延迟或周期性执行,二者的区别如下:

(1).Timer对调度的支持是基于绝对时间的,不支持相对时间,因此任务对系统时钟的改变是敏感的;ScheduledExecutorService只支持相对时间。

(2).Timer只创建唯一的线程来执行所有的timer任务,若一个timer任务的执行很耗时,会导致其他的timer任务时效准确性问题,例如一个timer任务每10ms执行一次,而另一个timer任务每40ms执行一次,若按固定频率进行调度则重复出现的任务会在耗时的任务完成后快速联系地被调用4次,若按延迟进行调度则完全丢失4次调用。

ScheduledExecutorService可以提供多个线程来执行延迟或按固定频率执行的周期性任务,解决了Timer任务时效准确性问题。

(3).Timer任务抛出未检查异常时,Timer将会被异常地终止,Timer也不会再重新恢复线程执行,它错误地认为整个Timer都被取消了,从而产生无法预料的线程泄露:所有已被安排但尚未执行的Timer任务永远不会再执行了,新的任务也不能被调度了。

下面的例子代码演示Timer的线程泄露:

  1. public class OutOfTimer {  
  2.     public static void main(String[] args) throws Exception {  
  3.         Timer timer = new Timer();  
  4.         timer.schedule(new ThrowTask(), 1);  
  5.         TimeUnit.SECONDS.sleep(1);  
  6.         timer.schedule(new ThrowTask(), 1);  
  7.         TimeUnit.SECONDS.sleep(5);  
  8.     }  
  9.       
  10.     static class ThrowTask extends TimerTask{  
  11.         public void run(){  
  12.             System.out.println("I'm invoked.");  
  13.             throw new RuntimeException();  
  14.         }  
  15.     }  
  16. }  

上面代码运行后只会打印出一行I'm invoked.然后就抛出Timer already cancelled异常。

ScheduledExecutorService可以妥善地处理异常,避免线程泄露。

下面的例子代码演示ScheduledExecutorService在异常之后仍然可以继续运行:

  1. public class OutOfScheduledExecutor {  
  2.     public static void main(String[] args) throws Exception {  
  3.         ScheduledExecutorService service =                      Executors.newScheduledThreadPool(1);  
  4.         service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);  
  5.         TimeUnit.SECONDS.sleep(1);  
  6.         service.schedule(new ThrowTask(), 1, TimeUnit.SECONDS);  
  7.         TimeUnit.SECONDS.sleep(5);  
  8.         service.shutdown();  
  9.     }  
  10.       
  11.     static class ThrowTask implements Runnable{  
  12.         public void run(){  
  13.             System.out.println("I'm invoked.");  
  14.             throw new RuntimeException();  
  15.         }  
  16.     }  
  17. }  

上述的ScheduledExecutorService例子没有抛出,可以正常打印出两行I'm invoked.

5.CallableFuture

RunnableExecutor框架常用的任务基本表达形式,但是其run方法不能返回一个值或者抛出受检查的异常。

Callable类似于Runnable,其call方法可以等待返回值,并为可能抛出的异常预先做好准备。

Future描述了任务的生命周期,并提供了相关的方法来获得任务的结果、取消任务以及检验任务是否已经完成或者被取消。ExecutorService中所有的submit方法都返回一个Future

使用Runnable/CallableFuture可以提高任务的并行性,例子代码如下:

  1. public class FutureRender{  
  2.     private final ExecutorService executor = ......;  
  3.       
  4.     public void renderPage(CharSequence source){  
  5.         final List<ImageInfo> imageInfos = scanForImageInfo(source);  
  6.         Callable<List<ImageData>> task = new Callable<List<ImageData>>(){  
  7.             public list<ImageData> call(){  
  8.                 List<ImageData> result = new ArrayList<ImageData>();  
  9.                 for(ImageInfo imageInfo : imageInfos){  
  10.                     result.add(imageInfo.downloadImage());  
  11.                 }  
  12.                 return result;  
  13.             }  
  14.         };  
  15.         Future<List<ImageData>> future = executor.submit(task);  
  16.         renderText(source);  
  17.         try{  
  18.             List<ImageData> imageDatas = future.get();  
  19.             for(ImageData data : imageDatas){  
  20.                 renderImage(data);  
  21.             }  
  22.         }catch(InterruptedException e){  
  23.             Thread.currentThread().interrupt();  
  24.             future.cancel(true);  
  25.         }catch(ExecutionException e){  
  26.             throw launderThrowable(e.getCause());  
  27.         }  
  28.     }  
  29. }  

注意:只有大量相互独立且同类的任务进行并发处理时,会将程序的任务量分配到不同的任务中,才能正在获得并发性能的提高;而对异类任务的并发处理则会因为任务协调的开销,不一定能获得性能的提高。

6.CompletionService介绍:

CompletionService整合了ExecutorBlockingQueue的功能,可以将一个批处理任务提交给给它执行,然后返回一个包含每个任务执行结果的QueueingFuture队列,通过调用队列的takepoll方法,可以获得包含每个任务执行结果的Future

CompletionService的例子代码如下:

  1. public class CompletionServiceRender {  
  2.     private final ExecutorService executor;  
  3.   
  4.     public CompletionServiceRender(ExecutorService executor) {  
  5.         This.executor = executor;  
  6.     }  
  7.   
  8.     public void renderPage(CharSequence source) {  
  9.         final List<ImageInfo> imageInfos = scanForImageInfo(source);  
  10.         CompletionService<ImageData> service = new ExecutorCompletionService<ImageData>(  
  11.                 executor);  
  12.         for (final ImageInfo imageInfo : imageInfos) {  
  13.             service.submit(new Callable<ImageData>() {  
  14.                 public ImageData call() {  
  15.                     return imageInfo.downloadImage();  
  16.                 }  
  17.             });  
  18.         }  
  19.         renderText(source);  
  20.         try {  
  21.             for (int i = 0; i < imageInfos.size(); i++) {  
  22.                 Future<ImageDate> f = service.take();  
  23.                 ImageData data = f.get();  
  24.                 renderImage(data);  
  25.             }  
  26.         } catch (InterruptedException e) {  
  27.             Thread.currentThread().interrupt();  
  28.         } catch (ExecutionException e) {  
  29.             throw launderThrowable(e.getCause());  
  30.         }  
  31.     }  
  32. }  

7.线程的取消和关闭:

对于非后台线程,如果取消和关闭不当会导致阻塞JVM无法正常关闭,Java提供了一个协作的中断机制使一个线程能够要求另一个线程停止当前工作。

Java中常用的取消和关闭策略如下:

(1).非阻塞方法:

使用volatile域保存取消状态,在每次操作时检测该状态。

(2).阻塞方法:

线程可能永远不会检测取消标志,因此使用volatile域保存取消状态的方案不可行,需要使用线程中断。

线程中断是一个协作机制,一个线程给另一个线程发送信号,通知它在下一个方便时刻(通常称为取消点)停止正在做的工作,去做其他事情。

每个线程都有一个boolean类型的中断状态,在中断的时候该中断状态被设置为true,线程中断相关的方法如下:

  1. public class Thread{  
  2.     //中断目标线程  
  3.     public void interrupt(){......}  
  4.   
  5.     //返回目标线程的中断状态  
  6.     public boolean isInterrupted(){......}  
  7.   
  8.     //清除当前线程的中断状态,并返回它之前的值  
  9.     public static boolean interrupted(){......}  
  10.   
  11.     ......  
  12. }  

特定阻塞库类的方法都支持中断,中断通常是实现线程取消最明智的选择。

(3).ExecutorFuture

Executor线程池可以使用shutdownshutdownNow方法来关闭线程池。

Future可以使用cancel方法取消任务。

(4).JVM关闭钩子:

JVM正常关闭时,可以执行使用Runtime.addShutdownHook注册的尚未开始执行的线程(关闭钩子),例子代码如下:

  1. public void start(){  
  2.     Runtime.getRuntime().addShutdownHook(new Thread(){  
  3.         public void run(){  
  4.             try{  
  5.                 LogService.this.stop();  
  6.             }catch(InterruptedException ignore){  
  7.             }  
  8.         }  
  9.     });  
  10. }  

JVM关闭钩子全部是并发执行,因此必须是线程安全,访问共享数据必须要同步,同时小心避免死锁。

JVM关闭钩子常用于服务或应用程序的清理,或者清除OS不能自动清除的资源。

<think>好的,我现在需要向用户介绍Java线程池。首先,我应该回忆一下线程池的基本概念和作用,然后考虑Java线程池的具体实现方式。用户可能想知道为什么使用线程池线程池有什么好处,以及如何在实际项目中使用。 线程池的主要目的是减少线程创建和销毁的开销,提高系统性能。Java通过Executor框架提供了线程池的支持,核心接口是ExecutorService。常见的线程池类型有FixedThreadPool、CachedThreadPool、SingleThreadExecutor和ScheduledThreadPool。需要解释每种线程池的特点和适用场景。 然后,我应该详细说明线程池的关键参数,比如核心线程数、最大线程数、存活时间、工作队列和拒绝策略。这些参数如何影响线程池的行为,用户配置时需要注意什么。 接下来,可能要给一个简单的代码示例,展示如何创建线程池并提交任务。例如使用Executors工厂类创建FixedThreadPool,然后用execute或submit方法提交Runnable或Callable任务。 另外,线程池的生命周期管理也很重要,比如shutdown和shutdownNow方法的区别,以及如何正确关闭线程池,避免资源泄漏。 还要提到线程池的常见问题,比如线程泄漏、死锁、资源竞争,以及如何合理设置线程池大小。可以建议根据任务类型(CPU密集型或IO密集型)来调整线程数量。 最后,强调线程池的优势,比如资源复用、控制并发数、管理线程生命周期等,以及不当使用可能带来的风险,比如线程数过多导致内存溢出。 需要确保内容结构清晰,分点说明,使用用户容易理解的语言,避免过于技术化的术语,同时覆盖关键知识点。如果有不确定的地方,比如某些参数的默认值或具体实现细节,需要再次确认Java文档,保证准确性。</think>Java线程池是一种管理和复用线程的机制,能够有效提升多线程程序的性能和资源利用率。下面分步骤详细介绍: --- ### 一、为什么需要线程池? 1. **降低资源消耗** 频繁创建/销毁线程会消耗系统资源,线程池通过复用已有线程减少开销。 2. **提高响应速度** 任务到达时可直接使用空闲线程,无需等待线程创建。 3. **控制并发数量** 通过限制线程数量,防止资源竞争导致系统崩溃。 --- ### 二、线程池核心参数 Java线程池通过`ThreadPoolExecutor`类实现,构造函数包含以下关键参数: ```java public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 非核心线程空闲存活时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 任务队列 RejectedExecutionHandler handler // 拒绝策略 ) ``` --- ### 三、线程池工作流程 1. 提交任务后,优先使用核心线程执行。 2. 核心线程全忙时,任务进入工作队列。 3. 队列满后,创建非核心线程(不超过`maximumPoolSize`)。 4. 线程数达上限且队列已满,触发拒绝策略。 --- ### 四、常见线程池类型 通过`Executors`工厂类创建: 1. **FixedThreadPool** $$ \text{固定线程数} = \text{核心线程数} = \text{最大线程数} $$ 适用于负载较重的服务器。 2. **CachedThreadPool** $$ \text{线程数动态调整,空闲线程60秒回收} $$ 适合短时异步任务。 3. **SingleThreadExecutor** $$ \text{单线程顺序执行任务} $$ 保证任务顺序性。 4. **ScheduledThreadPool** 支持定时/周期性任务。 --- ### 五、拒绝策略 当线程池无法处理新任务时,触发以下策略: 1. **AbortPolicy**(默认):抛出`RejectedExecutionException`。 2. **CallerRunsPolicy**:由提交任务的线程直接执行。 3. **DiscardPolicy**:静默丢弃新任务。 4. **DiscardOldestPolicy**:丢弃队列中最老的任务。 --- ### 六、使用示例 ```java // 创建固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(4); // 提交Runnable任务 executor.execute(() -> { System.out.println("Task executed by " + Thread.currentThread().getName()); }); // 提交Callable任务并获取Future Future<Integer> future = executor.submit(() -> { return 42; }); // 关闭线程池(不再接受新任务) executor.shutdown(); ``` --- ### 七、最佳实践 1. **合理设置线程数** - CPU密集型任务:$$ \text{线程数} = \text{CPU核心数} + 1 $$ - IO密集型任务:$$ \text{线程数} = \text{CPU核心数} \times 2 $$ 2. **避免使用无界队列** 防止内存溢出(如默认的`LinkedBlockingQueue`无界)。 3. **显式关闭线程池** 调用`shutdown()`或`shutdownNow()`,否则可能导致线程泄漏。 --- 通过合理使用线程池,可以在保证系统稳定性的同时,充分发挥多线程编程的优势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值