Java线程池

在现代Java应用开发中,线程池(ThreadPool)是一种非常重要和常用的并发编程工具。它不仅能够有效地管理线程的生命周期,还能通过复用线程来减少线程创建和销毁的开销,从而显著提高系统的性能和资源利用率。本文将深入探讨Java线程池的原理、使用方法以及最佳实践。

目录

一、线程池的原理

1.1 线程池的基本概念

1.2 Java中的线程池实现

1.3 线程池的工作流程

二、线程池的使用

2.1 创建线程池

2.2 提交任务

2.3 关闭线程池

三、线程池的使用注意

3.1 合理配置线程池参数

3.2 避免线程泄漏

3.3 监控和调优

3.4 线程池的选择

总结


一、线程池的原理

1.1 线程池的基本概念

线程池是一种基于池化技术设计用来管理线程的工具。它维护了一组线程,这些线程可以并行地执行多个任务。当有新任务到来时,线程池会尝试将任务分配给空闲的线程执行;如果所有线程都在忙,则根据配置的策略(如阻塞等待、拒绝服务等)处理新任务。

1.2 Java中的线程池实现

Java在java.util.concurrent包中提供了多种线程池的实现,其中最核心的是ExecutorService接口。ExecutorService提供了丰富的线程池管理功能,包括任务的提交、任务的取消、获取任务的执行结果等。Java通过Executors工厂类提供了几种常用的线程池实现:

  • FixedThreadPool:固定大小的线程池,线程数量在初始化时设定,且不会改变。
  • CachedThreadPool:可缓存的线程池,线程数量不固定,根据需要动态地创建和销毁线程。
  • SingleThreadExecutor:单线程的线程池,保证所有任务在同一线程中按顺序执行。
  • ScheduledThreadPool:支持定时或周期性任务的线程池。

1.3 线程池的工作流程

线程池的工作流程大致如下:

  1. 任务提交:通过调用ExecutorServicesubmit()execute()方法提交任务。
  2. 任务缓存:如果当前线程池中的线程数量未达到核心线程数(对于FixedThreadPoolCachedThreadPool),则立即创建新线程执行任务;如果达到核心线程数,则将任务放入工作队列中等待执行。
  3. 任务执行:线程池中的线程会不断从工作队列中取出任务并执行。
  4. 线程复用:当任务执行完毕后,线程不会立即销毁,而是会回到线程池中等待下一个任务。
  5. 线程销毁:如果线程池中的线程数量超过了核心线程数,并且这些线程在指定时间内没有执行任务,则这些线程会被销毁。

二、线程池的使用

2.1 创建线程池

使用Executors工厂类可以方便地创建不同类型的线程池:

// 创建一个固定大小的线程池 
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); 


// 创建一个可缓存的线程池 
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 


// 创建一个单线程的线程池 
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); 


// 创建一个支持定时和周期性任务的线程池 
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

2.2 提交任务

提交任务到线程池有两种方式:execute()submit()execute()方法用于提交不需要返回结果的任务,而submit()方法用于提交需要返回结果的任务。submit()方法会返回一个Future对象,通过该对象可以获取任务执行的结果。

// 使用execute()提交任务 
fixedThreadPool.execute(() -> { 
// 执行任务 
}); 


// 使用submit()提交任务并获取结果 
Future<Integer> future = fixedThreadPool.submit(() -> { 
// 执行任务并返回结果 
return 123; 
}); 


// 获取任务执行结果 
try { 
Integer result = future.get(); // 阻塞等待任务执行完成并获取结果 
System.out.println(result); 
} catch (InterruptedException | ExecutionException e) { 
e.printStackTrace(); 
}

2.3 关闭线程池

当不再需要线程池时,应该通过调用shutdown()shutdownNow()方法来关闭它。shutdown()方法会等待所有任务执行完毕后关闭线程池,而shutdownNow()方法会尝试停止所有正在执行的任务,并返回等待执行的任务列表。


	fixedThreadPool.shutdown(); // 等待所有任务执行完毕 

	// 或者 

	fixedThreadPool.shutdownNow(); // 尝试停止所有任务

三、线程池的使用注意

3.1 合理配置线程池参数

  • 工作队列:根据任务的特性和预期吞吐量来选择合适的工作队列。常用的工作队列有ArrayBlockingQueue(基于数组的阻塞队列,需要指定容量)、LinkedBlockingQueue(基于链表的阻塞队列,通常不指定容量,默认为Integer.MAX_VALUE)、SynchronousQueue(一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作)等。

  • 线程存活时间:当线程池中的线程数量超过核心线程数时,这是非核心线程在终止前等待新任务的最长时间。如果设置为0,则表示非核心线程会立即终止。

  • 拒绝策略:当工作队列已满且线程池中的线程数已达到最大线程数时,需要一种策略来处理新提交的任务。Java提供了四种内置的拒绝策略:

    • ThreadPoolExecutor.AbortPolicy:默认策略,直接抛出RejectedExecutionException
    • ThreadPoolExecutor.CallerRunsPolicy:用调用者所在的线程来执行任务。
    • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最早的任务,然后尝试再次提交当前任务。
    • ThreadPoolExecutor.DiscardPolicy:静默地丢弃无法处理的任务,不抛出任何异常。

3.2 避免线程泄漏

线程泄漏通常发生在长时间运行的应用中,由于某些原因(如任务中创建了新的线程但未能正确管理,或者任务中的某些资源(如数据库连接、文件句柄)未能正确释放),导致线程或资源无法被回收。在使用线程池时,应确保所有任务都能正确完成,并且不会创建额外的线程或资源。

3.3 监控和调优

  • 监控线程池状态:通过JMX(Java Management Extensions)或第三方监控工具来监控线程池的状态,包括线程数、队列大小、已完成任务数、拒绝任务数等。

  • 性能调优:根据监控数据调整线程池的参数,如核心线程数、最大线程数、工作队列大小等,以达到最佳的性能和资源利用率。

  • 错误处理和日志记录:为线程池和任务执行添加适当的错误处理和日志记录,以便在出现问题时能够快速定位和解决。

3.4 线程池的选择

  • 对于计算密集型任务,应使用固定大小的线程池,以充分利用CPU资源。
  • 对于IO密集型任务,可以使用可缓存的线程池,以便在需要时动态地创建和销毁线程。
  • 对于需要按顺序执行的任务,应使用单线程的线程池。
  • 对于需要定时或周期性执行的任务,应使用定时线程池。

四、手动实现线程池

public interface ThreadPool<Job extends Runnable> {
    //执行一个job
    void execute(Job job);
    //关闭线程池
    void shtdown();
    //增加工作线程
    void addWorkers(int num);
    //减少工作线程
    void removeWorker(int num) throws IllegalAccessException;
    //得到正在等待执行的任务数量
    int getJobSize();
}
public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job>{
    //最大线程数
    private static final int MAX_WORKER_NUMBERS = 10;
    //核心线程数
    private static final int DEFAULT_WORKER_NUMBERS = 5;
    //最小线程数
    private static final int MIN_WORKER_NUMBERS = 1;
    //任务队列
    private final LinkedList<Job> jobs = new LinkedList<Job>();
    //线程列表
    private final List<Worker> workers = Collections.synchronizedList(new ArrayList<>());
    //线程数量
    private int workerNumber = DEFAULT_WORKER_NUMBERS;
    //线程编号
    private AtomicLong threadNumber = new AtomicLong();

    public DefaultThreadPool() {
        initializeWokers(workerNumber);
    }

    public DefaultThreadPool(int num) {
        if (num > MAX_WORKER_NUMBERS){
            workerNumber = MAX_WORKER_NUMBERS;
        }else if(num < MIN_WORKER_NUMBERS){
            workerNumber = MIN_WORKER_NUMBERS;
        }else {
            workerNumber = num;
        }
        initializeWokers(workerNumber);
    }

    private void initializeWokers(int workerNumber) {
        for (int i = 0; i < workerNumber; i++) {
            Worker worker = new Worker();
            workers.add(worker);
            Thread thread = new Thread(worker,"ThreadPool-worker-"+threadNumber.incrementAndGet());
            thread.start();
        }
    }

    @Override
    public void execute(Job job) {
        if(job != null){
            synchronized (jobs){
                jobs.add(job);
                jobs.notify();
            }
        }
    }

    @Override
    public void shtdown() {
        for (Worker worker : workers) {
            worker.shutdown();
        }
    }

    @Override
    public void addWorkers(int num) {
        synchronized (jobs){
            if (num + this.workerNumber > MAX_WORKER_NUMBERS){
                num = MAX_WORKER_NUMBERS - this.workerNumber;
            }
            initializeWokers(num);
            this.workerNumber += num;
        }
    }

    @Override
    public void removeWorker(int num) throws IllegalAccessException {
        synchronized (jobs){
            if(num >= this.workerNumber){
                throw  new IllegalAccessException("beyond workNum");
            }
            int count = 0;
            while (count < num){
                Worker worker = workers.get(count);
                if (workers.remove(worker)){
                    worker.shutdown();
                    count++;
                }
            }
            this.workerNumber -= num;
        }
    }

    @Override
    public int getJobSize() {
        return jobs.size();
    }

    public int getWorkerNumber() {
        return workerNumber;
    }

    class Worker implements Runnable{

        private volatile  boolean running = true;

        @Override
        public void run() {
            while (running){
                Job job = null;
                synchronized (jobs){
                    while (jobs.isEmpty()){
                        try{
                            jobs.wait();
                        }catch (InterruptedException e){
                            Thread.currentThread().interrupt();
                            return;
                        }
                    }
                    job = jobs.removeFirst();
                }
                if (job!=null){
                    try{
                        job.run();
                    }catch (Exception e){
                    }
                }
            }
        }
        public void shutdown(){
            running = false;
        }
    }
}

 

总结

Java线程池是并发编程中的一个重要工具,通过合理使用线程池,可以显著提高系统的性能和资源利用率。然而,要充分利用线程池的优势,需要深入理解其原理、掌握其使用方法,并遵循最佳实践。希望本文能为读者在使用Java线程池时提供一些有用的指导和参考。

  • 22
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java线程池是一种用于管理和复用线程的机制,它可以提高多线程应用程序的性能和效率。线程池中的线程可以被重复使用,避免了频繁创建和销毁线程的开销。 在Java中,线程池可以通过`ExecutorService`接口来创建和管理。线程池中的线程可以执行提交给它的任务,并且可以根据需要自动创建新的线程或销毁闲置的线程。 嵌套线程池是指在一个线程池中创建另一个线程池。这种情况通常发生在需要处理一些复杂的任务,其中每个任务本身也需要使用线程池来执行。 下面是一个示例代码,演示了如何在Java中嵌套使用线程池: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class NestedThreadPoolExample { public static void main(String[] args) { // 创建外层线程池 ExecutorService outerThreadPool = Executors.newFixedThreadPool(5); // 提交任务给外层线程池 outerThreadPool.execute(() -> { // 创建内层线程池 ExecutorService innerThreadPool = Executors.newFixedThreadPool(3); // 提交任务给内层线程池 innerThreadPool.execute(() -> { // 内层线程池执行的任务 System.out.println("Inner thread pool task executed"); }); // 关闭内层线程池 innerThreadPool.shutdown(); }); // 关闭外层线程池 outerThreadPool.shutdown(); } } ``` 在上面的示例中,我们首先创建了一个外层线程池`outerThreadPool`,它使用`Executors.newFixedThreadPool()`方法创建了一个固定大小的线程池。然后,我们向外层线程池提交了一个任务,该任务在执行时创建了一个内层线程池`innerThreadPool`,并向内层线程池提交了一个任务。最后,我们分别关闭了内层线程池和外层线程池

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值