java线程池的使用

线程池概述

线程池(Thread Pool)是一种基于池化思想管理线程的工具。使用线程池可以带来诸多好处:
①降低资源消耗:通过池化技术复用已创建的线程,减少线程创建和销毁的损耗。
②提高响应速度:任务到达时,特定情况下无需再创建线程。
③便于管理。
java中线程池相关的接口和类主要包括:
Executor是线程执行器
Runnable接口定义任务逻辑,通过这两个接口将任务的定义和执行分离开来。
ThreadPoolExecutor实现ExecutorService,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等服务
Executors中提供了一些默认提供的线程池,都是通过参数设置来实现不同的线程池机制

线程池的七个参数

核心构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize: 核心线程数
maximumPoolSize:最大线程数
keepAliveTime:核心线程除外的空闲线程存活的最大时间,之后归还给OS
unit:keepAliveTime的单位
workQueue:阻塞任务队列
threadFactory:新建线程工厂
handler:拒绝策略,jdk默认提供四种,但是生产环境一般需要自定义实现
下面通过任务调度execute方法研究下参数间的关系

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //worker数量(理解为线程池中线程数量)比核心线程数corePoolSize少时,直接创建worker执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //此时worker数量>核心线程数corePoolSize
        //将任务加到阻塞队列中,由线程池去调度
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //此时如果线程池依旧是运行状态,那么就是任务队列已满
        //尝试新加非核心worker去执行任务
        //执行任务失败或可能是非核心worker大于maximumPoolSize,则执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

总结一下:
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

拒绝策略

当线程池任务队列已满并且线程数量达到maximumPoolSize,线程池执行拒绝策略。
拒绝策略接口:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

jdk提供的四种策略介绍:
CallerRunsPolicy:由提交任务的线程取处理。这种情况是需要让所有任务都执行完毕。
AbortPolicy: 直接丢弃任务并抛出java.util.concurrent.RejectedExecutionException异常
DiscardPolicy:丢弃任务但不抛出异常
DiscardOldPolicy:丢弃任务最前面的异常,并重新提交被拒绝的任务。
一般生产环境都需要实现自定义的拒绝策略,将任务保存起来。

默认提供的四个线程池

1.单个线程的线程池(newSingleThreadExecutor)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadPool {
	public static void main(String[] args) {
		ExecutorService service = Executors.newSingleThreadExecutor();
		for(int i=0; i<5; i++) {
			final int j = i;
			service.execute(()->{
				System.out.println(j + " " + Thread.currentThread().getName());
			});
		}
		service.shutdown();	
	}
}

SingleThreadExecutor创建的是一个核心线程数和最大线程数都是1,任务队列是LinkedBlockingQueue的无界队列 的线程池。单线程的线程池可以保证队列中的任务按照指定顺序执行。

2.固定线程数的线程池(newFixedThreadPool)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPool {
    public static void main(String[] args) {
    	// 获取计算机有几个核
        int processors = Runtime.getRuntime().availableProcessors();
        // 固定个数的线程池,可以为每个CPU核绑定一定数量的线程数
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(processors * 5);
        for (int i = 0; i < 10; i++) {
            fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        fixedThreadPool.shutdown();
    }
}

newFixedThreadPool创建的是一个固定线程数(核心线程数和最大线程数都是同一个指定值),任务队列是LinkedBlockingQueue的无界队列的线程池。线程池中最多有nThreads个线程处于活动状态,当线程中所有线程都处于活动状态时,新任务会加入无界队列等待执行,任务永远不会被拒绝,不会创建核心线程数意外的线程,线程也不会被销毁,适合平稳且固定的任务开发场景。

3.缓存的线程池(newCachedThreadPool)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CachedPool {
	public static void main(String[] args) throws InterruptedException {
		ExecutorService service = Executors.newCachedThreadPool();
		System.out.println(service);
		for (int i = 0; i < 2; i++) {
			service.execute(() -> {
				try {
					TimeUnit.MILLISECONDS.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}				
				System.out.println(Thread.currentThread().getName());				
			});
		}
		service.shutdown();	
	}
}

CachedThreadPool创建的是一个核心线程数为0,最大线程数为最大整型值,任务队列是SynchronousQueue的线程池。SynchronousQueue队列的特点是他的take方法需要put方法等待,put方法需要需要taken方法等待,否则提交任务线程就会一直阻塞。队列是没有存储空间的,需要找到一个空闲线程去处理任务,如果当前没有空闲线程的话就新建线程处理,线程空闲超过60s就会被回收杀死。适合任务执行时间短,任务数量不明确的场景。他的缺点是如果提交任务数量远大于处理速度,就会不断创建线程耗尽系统资源。

4.定时任务线程池(newScheduledThreadPool)

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledPool {
	public static void main(String[] args) {
	//指定核心线程数
		ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
		service.scheduleAtFixedRate(()->{
			try {
				TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName());
		}, 0, 500, TimeUnit.MILLISECONDS);		
	}
}

ScheduledThreadPool是创建一个指定核心线程数,最大线程数为最大整型值,任务队列是DelayedWorkQueue的线程池。

<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**:支持定时/周期性任务。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值