java线程池的使用

一、为什么要使用线程池?

我们许多服务器应用程序的会面对着处理大量短时间任务的情况。我们可以选择创建多个线程来处理,这也是一种比较好的解决方式,但也存在一个明显的缺点,创建和销毁线程需要消耗大量的资源,甚至服务器在创建和销毁线程上花费的时间和系统资源比线程执行任务所花费的时间和系统资源还要多。
线程池就很好的解决了这种问题。线程池用一定数量的线程执行了超出线程数的任务。同时,每个线程执行了任务后必不会马上被销毁,而是接着进行下一个任务,这极大的减少了创建和销毁线程所消耗的资源。因为执行的线程数不多,也能减少出现内存不足的可能。

二、工作原理

线程池中的线程分为核心线程和普通线程。具体处理流程如下:
1、当任务进入线程池时,首先判断核心线程是否全部在执行其他任务,如果有空闲的核心线程,就用核心线程执行该任务。
2、如果核心任务全部都在执行其他任务(核心线程已满),就将任务放进阻塞队列中。
3、如果阻塞队列也满了,就将任务交给空闲的普通线程执行。
4、如果整个线程池和阻塞队列全部满了的话,我们要选择饱和策略处理这种情况(具体往后看)。
在这里插入图片描述
关于阻塞队列,有三种策略:
1、直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程,此策略可以避免出现死锁的情况。直接提交要求线程池无界。
2、无界队列。即当核心线程已满时,请求的任务会全部进入阻塞队列。该种队列意味着普通线程已经没有意义,同时也没有必要考虑饱和问题。但这个队列有一个很大的缺点,即大大增加了死锁的可能性,所有无界队列一般用于任务之间相互独立,任务执行时互不影响时使用。
3、有界队列。有界队列有助于防止资源耗尽,但较难调整和控制。使用时需要对队列长度和线程池大小进行折中,使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

关于饱和策略,我们也介绍几种方法:
1、Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
2、CallerRuns策略:为调节机制,既不抛出异常也不丢弃任务,而是选择将任务返还给调用者。
3、Discard策略:直接丢弃任务。
4、DiscardOldest策略:将队列中等待最久的任务(即队列最前面的任务)和新任务替换。

三、ThreadPoolExecutor

说起线程池的创建,不得不提其核心的类ThreadPoolExecutor。
首先我们先来看看其构造方法:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) 

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

我们来一一解释参数的意思,就能大致明白这个类了:
corePoolSize:线程池中核心线程数量。
maximumPoolSize:线程池中线程总数量。
keepAliveTime:普通线程的最长空闲时间(若普通线程空闲时间超出keepAliveTime,则会被销毁),只有corePoolSize小于maximumPoolSize时才有意义。
unit:空闲时间所用单位,如TimeUnit.SECONDS,TimeUnit.DAYS等。
workQueue:用于存放任务的队列,即阻塞队列。
threadFactory:线程工厂,无需手工编写new Thread()的调用。
handler:饱和策略。
具体使用参考下列代码:

public class ThreadPool {

	public static void main(String[] args){
		LinkedBlockingQueue<Runnable> queue = 
				new LinkedBlockingQueue<Runnable>(8);  //创建长度为8的阻塞队列
		ThreadPoolExecutor threadpool = new ThreadPoolExecutor(5,10,1,TimeUnit.MINUTES,queue);
		for(int i=0;i<30;i++){
			threadpool.execute(new Threadnow());
			System.out.println("线程池中运行的线程数: " + threadpool.getPoolSize());
			if(queue.size()>0)
				System.out.println("队列中阻塞的线程数:"+queue.size());
		}
		threadpool.shutdown();
	}
}

public class Threadnow implements Runnable{
	public void run(){
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

运行结果:
在这里插入图片描述

该代码创建了一个核心线程为5,总线程数量为10的线程池,阻塞队列长度为8。从运行结果来看,开始时核心线程运行,满了以后,任务放进阻塞队列,阻塞队列满了后,任务由普通线程执行,当任务数超过线程池总数时,抛出RejectedExecutionException。

四、几种线程池

ThreadPoolExecutor我们一般用于想手动配置和调整线程池时,事实上,更加建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool(), Executors.newFixedThreadPool(),Executors.newSingleThreadExecutor()。
1、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
我们直接看一下源码:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

从源码中可以看出,newCachedThreadPool事实上就是我们之前所说的队列策略中的直接提交策略。它没有核心进程,进程池的数目无限大,队列是SynchronousQueue,即来了任务就创建执行。虽然该线程池可以不断创建新的线程,空闲1分钟后线程自动销毁,但我们要避免让过多任务同时工作,进而造成系统瘫痪。
2、newFixedThreadPool
创建一个指定工作线程数量的线程池。

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

可以看出,newFixedThreadPoo就是队列策略中的无界队列。它没有普通线程,阻塞队列是LinkedBlockingQueue,没有界限。我们可以控制线程的最大并发数,同时节省线程创建时的开销。但因为没有普通线程的原因,核心线程是不会被销毁的,即可能会让线程处于长期空闲状态却不被销毁,白白占用内存空间。
3、newSingleThreadExecutor
创建单个工作线程的线程池。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

该线程池只有一个线程,阻塞队列是无界队列,可以保证线程按照指定的顺序进行执行,同时不会出现多个进程同时运行的情况。如果这个线程异常结束,会创建一个新的线程来取代它,保证顺序执行。

五、线程池的风险

1、死锁
事实上多线程机制都有可能发生死锁,但线程池多了一种发生死锁的可能,即线程池中某线程需要等待另一个线程执行的结果才可运行,但该线程却有可能在阻塞队列中,虽然该线程一般来说迟早会执行,但可能所有在执行的线程都在等待阻塞队列中的线程的执行结果,会让整个线程池瘫痪。
2、线程泄露
线程泄露指的是当从池中除去一个线程去执行任务,但该任务最后却没有回到池中,一般是发生异常。线程泄露会让线程池永远少一个线程。

<think>好的,我需要回答用户关于如何在Java使用线程池的问题,包括示例代码和最佳实践。首先,我应该回顾用户提供的引用内容,看看有没有相关的信息可以利用。 根据引用[1],提到了Java线程池使用实例、最佳实践以及代码示例。引用[2]讨论了为什么使用线程池、如何使用以及遇到的坑。引用[3]则集中在最佳实践和异常处理方面。这些引用应该能提供足够的信息来构建回答。 首先,我需要解释线程池的基本概念和为什么要使用线程池,比如资源复用、控制并发数、管理线程生命周期等。然后,介绍Java中创建线程池的方法,特别是通过Executors工厂类提供的几种常见线程池类型,比如FixedThreadPool、CachedThreadPool、SingleThreadExecutor等,但也要提到推荐直接使用ThreadPoolExecutor来创建,以便更灵活地控制参数,避免资源耗尽的风险,这点在引用[3]中有提到。 接下来,给出一个示例代码。根据引用[1]中的示例,可能需要展示如何通过ThreadPoolExecutor来创建线程池,设置核心线程数、最大线程数、队列容量等参数,并演示如何提交任务。同时,需要强调在阿里巴巴开发规范中推荐手动创建线程池,而不是使用Executors,这点可能来自引用[3]中的最佳实践。 然后,讨论最佳实践,比如合理配置线程数、使用有界队列、处理未捕获异常、正确关闭线程池等。这些内容在引用[3]中有涉及,比如异常处理和分析的重要性,以及关闭线程池的方法。 此外,要注意线程池的任务拒绝策略,如AbortPolicy、CallerRunsPolicy等,并说明在不同场景下如何选择合适的策略。这部分可能需要结合引用[2]中提到的使用过程中遇到的坑。 最后,生成相关问题,可能包括线程池的参数配置、不同线程池类型的区别、如何监控线程池状态、常见的线程池问题排查等,确保问题与用户查询内容相关,并帮助深化理解。 需要确保回答结构清晰,分步骤说明,代码正确,并且引用正确的位置。例如,在提到线程池参数配置时,引用[3]中的最佳实践;在提到任务拒绝策略时,引用[2]中的实际应用经验。 现在需要将以上思考整理成中文回答,确保符合用户要求的格式,使用正确的LaTeX语法,并在适当的位置添加引用标识。</think>### 如何在Java使用线程池? #### 1. **线程池的作用与优势** - **资源复用**:避免频繁创建/销毁线程,提升性能[^2]。 - **控制并发**:通过核心线程数、最大线程数限制资源占用[^3]。 - **任务管理**:使用队列缓冲任务,支持拒绝策略处理过载[^1]。 #### 2. **创建线程池** Java推荐通过`ThreadPoolExecutor`手动配置参数(而非`Executors`工厂类,避免潜在风险)[^3]: ```java int corePoolSize = 5; // 核心线程数 int maxPoolSize = 10; // 最大线程数 long keepAliveTime = 60L; // 空闲线程存活时间(秒) BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100); // 有界队列 ThreadPoolExecutor executor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.SECONDS, queue, new ThreadPoolExecutor.AbortPolicy() // 默认拒绝策略:直接抛出异常 ); ``` #### 3. **提交任务** - 使用`execute()`提交`Runnable`任务: ```java executor.execute(() -> { System.out.println("Task executed by " + Thread.currentThread().getName()); }); ``` - 使用`submit()`提交`Callable`任务(可获取返回值): ```java Future<String> future = executor.submit(() -> { return "Result from thread " + Thread.currentThread().getName(); }); System.out.println(future.get()); // 阻塞获取结果 ``` #### 4. **最佳实践** - **参数配置**: - CPU密集型任务:核心线程数 ≈ CPU核心数[^3]。 - I/O密集型任务:核心线程数可适当增加(如2倍CPU核心数)。 - 使用有界队列避免内存溢出[^3]。 - **异常处理**: ```java executor.setRejectedExecutionHandler((r, executor) -> { // 自定义拒绝策略,如记录日志或降级处理 }); // 任务内捕获异常 executor.execute(() -> { try { // 业务逻辑 } catch (Exception e) { // 记录异常 } }); ``` - **关闭线程池**: ```java executor.shutdown(); // 等待已提交任务完成 // executor.shutdownNow(); // 立即终止所有任务(慎用) ``` #### 5. **常用线程池类型** - **FixedThreadPool**:固定线程数,适用于负载较重的场景。 ```java ExecutorService fixedPool = Executors.newFixedThreadPool(5); ``` - **CachedThreadPool**:弹性线程数,适合短时异步任务[^1]。 - **ScheduledThreadPool**:支持定时/周期性任务。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值