线程池和使用

简介: 线程池是一种用于管理和复用线程的机制。在多线程应用程序中,线程的创建和销毁需要消耗大量的系统资源,而线程池可以通过预先创建一定数量的线程,然后将任务分配给这些线程来避免频繁地创建和销毁线程,从而提高应用程序的性能和效率。线程池还可以控制并发线程的数量,避免过多的线程竞争资源导致的性能下降和系统崩溃。线程池是多线程编程中常用的一种技术,被广泛应用于各种类型的应用程序中。

tip: 作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。

一、线程池的目的

线程池是一种用于管理和复用线程的机制。在多线程应用程序中,线程的创建和销毁需要消耗大量的系统资源,而线程池可以通过预先创建一定数量的线程,然后将任务分配给这些线程来避免频繁地创建和销毁线程,从而提高应用程序的性能和效率。线程池还可以控制并发线程的数量,避免过多的线程竞争资源导致的性能下降和系统崩溃。线程池是多线程编程中常用的一种技术,被广泛应用于各种类型的应用程序中。

二、线程池的参数

线程池的参数包括以下几个:

  1. 核心线程数(corePoolSize):线程池中最少保持的线程数。当有新任务提交时,如果当前线程池中的线程数小于核心线程数,则会创建新的线程来处理任务,即使有空闲的线程也会创建新的线程。

  2. 最大线程数(maximumPoolSize):线程池中最多能创建的线程数。当任务数量超过核心线程数时,线程池会创建新的线程来处理任务,但是最多只能创建最大线程数个线程。

  3. 空闲线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数时,空闲线程的存活时间。如果线程在这段时间内没有处理任务,则会被销毁,直到线程池中的线程数量不超过核心线程数。

  4. 阻塞队列(workQueue):用于存放等待执行的任务的队列。当线程池中的线程数量已经达到核心线程数时,新的任务会被放入阻塞队列中等待执行。

  5. 线程工厂(threadFactory):用于创建新的线程。

  6. 时间单位(TimeUnit): 针对救急线程,unit 时间单位

  7. 拒绝策略(rejectedExecutionHandler):当线程池中的线程数量已经达到最大线程数,且阻塞队列已经满了时,新的任务将无法被处理。拒绝策略用于处理这种情况,可以选择抛出异常、直接丢弃任务、丢弃队列中最早的任务或者在调用线程中执行任务等方式。

    这些参数可以根据具体应用程序的需求进行调整,以达到最佳的性能和效率。

package com.pany.camp.thread.pool;

import java.util.concurrent.*;

public class ThreadPoolExample {
   

    public static void main(String[] args) {
   
        // 创建一个线程池
        ExecutorService executor = new ThreadPoolExecutor(
                1,
                1,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // 提交10个任务给线程池
        for (int i = 1; i <= 10; i++) {
   
            final int taskId = i;
            executor.execute(new Runnable() {
   
                public void run() {
   
                    System.out.println("Task " + taskId + " is running in thread " + Thread.currentThread().getName());
                    try {
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }
                    System.out.println("Task " + taskId + " is completed");
                }
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

在这里插入图片描述

几种封装的线程池

以下是几种常见的封装的线程池:

  1. FixedThreadPool:固定大小线程池,线程数量固定,不会自动调整线程数量。适用于负载比较稳定的情况。
package com.pany.camp.thread.pool;

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

public class FixedThreadPoolExample {
   

    public static void main(String[] args) {
   
        // 创建一个固定大小的线程池,大小为3
        ExecutorService executor = Executors.newFixedThreadPool(3);
        // 提交6个任务给线程池
        for (int i = 1; i <= 6; i++) {
   
            final int taskId = i;
            executor.execute(new Runnable() {
   
                public void run() {
   
                    System.out.println("Task " + taskId + " is running in thread " + Thread.currentThread().getName());
                    try {
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }
                    System.out.println("Task " + taskId + " is completed");
                }
            });
        }
        // 关闭线程池
        executor.shutdown();
    }
}

我们创建了一个固定大小为3的线程池,然后提交了6个任务给线程池。每个任务都会打印出自己的编号和当前运行的线程名,并且在执行完任务后打印出完成信息。由于线程池大小为3,因此只有3个任务会同时运行,其他任务需要等待空闲线程。最后,我们调用了 executor.shutdown() 方法来关闭线程池。

  1. CachedThreadPool:可缓存线程池,线程数量不固定,会根据任务数量自动调整线程数量。适用于负载比较不稳定的情况。
package com.pany.camp.thread.pool;

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

public class CachedThreadPoolExample {
   

    public static void main(String[] args) {
   
        // 创建一个可缓存的线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        // 提交10个任务给线程池
        for (int i = 1; i <= 10; i++) {
   
            final int taskId = i;
            executor.execute(new Runnable() {
   
                public void run() {
   
                    System.out.println("Task " + taskId + " is running in thread " + Thread.currentThread().getName());
                    try {
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }
                    System.out.println("Task " + taskId + " is completed");
                }
            });
        }
        // 关闭线程池
        executor.shutdown();
    }
}

我们创建了一个可缓存的线程池,然后提交了10个任务给线程池。每个任务都会打印出自己的编号和当前运行的线程名,并且在执行完任务后打印出完成信息。由于线程池是可缓存的,因此会根据任务数量自动调整线程数量,如果有空闲线程可以使用,则会直接使用空闲线程,否则会创建新的线程。最后,我们调用了 executor.shutdown() 方法来关闭线程池。

  1. SingleThreadExecutor:单线程线程池,只有一个线程在工作,保证任务按照指定顺序执行。
package com.pany.camp.thread.pool;

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

public class SingleThreadExecutorExample {
   
    public static void main(String[] args) {
   
        // 创建一个单线程的线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 提交5个任务给线程池
        for (int i = 1; i <= 5; i++) {
   
            final int taskId = i;
            executor.execute(new Runnable() {
   
                public void run() {
   
                    System.out.println("Task " + taskId + " is running in thread " + Thread.currentThread().getName());
                    try {
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
   
                        e.printStackTrace();
                    }
                    System.out.println("Task " + taskId + " is completed");
                }
            });
        }
        // 关闭线程池
        executor.shutdown();
    }
}

我们创建了一个单线程的线程池,然后提交了5个任务给线程池。每个任务都会打印出自己的编号和当前运行的线程名,并且在执行完任务后打印出完成信息。由于线程池只有一个线程,因此所有任务都会按照顺序依次执行。最后,我们调用了 executor.shutdown() 方法来关闭线程池。

  1. ScheduledThreadPool:定时任务线程池,适用于需要定时执行任务的场景。
package com.pany.camp.thread.pool;

import cn.hutool.core.thread.ThreadUtil;

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

public class ScheduledThreadPoolExample {
   
    public static void main(String[] args) {
   
        // 创建一个大小为3的定时线程池
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
        // 延迟1秒后执行任务
        executor.schedule(() -> System.out.println("Task 1 is running in thread " + Thread.currentThread().getName()), 1, TimeUnit.SECONDS);
        // 延迟2秒后执行任务,之后每3秒执行一次
        executor.scheduleAtFixedRate(() -> System.out.println("Task 2 is running in thread " + Thread.currentThread().getName()), 2, 3, TimeUnit.SECONDS);
        // 延迟3秒后执行任务,之后每5秒执行一次
        executor.scheduleWithFixedDelay(() -> System.out.println("Task 3 is running in thread " + Thread.currentThread().getName()), 3, 5, TimeUnit.SECONDS);
        // 关闭线程池
        ThreadUtil.sleep(10000);
        executor.shutdown();
    }
}

我们创建了一个大小为3的定时线程池,然后提交了3个任务给线程池。第一个任务会延迟1秒后执行,第二个任务会延迟2秒后执行,并且之后每3秒执行一次,第三个任务会延迟3秒后执行,并且之后每5秒执行一次。每个任务都会打印出自己的编号和当前运行的线程名。最后,我们调用了 executor.shutdown() 方法来关闭线程池。

  1. WorkStealingPool:工作窃取线程池,线程数量固定,但是线程之间可以窃取任务执行,提高了任务执行的效率。
package com.pany.camp.thread.pool;

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

public class WorkStealingPoolExample {
   
    public static void main(String[] args) throws InterruptedException {
   
        // 获取当前系统可用的处理器数量
        int processors = Runtime.getRuntime().availableProcessors();
        // 创建一个工作窃取线程池
        ExecutorService executor = Executors.newWorkStealingPool(processors);
        // 提交10个任务给线程池
        IntStream.range(1, 11).forEach(i -> executor.submit(() -> {
   
            System.out.println("Task " + i + " is running in thread " + Thread.currentThread().getName());
            try {
   
                Thread.sleep(1000);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            System.out.println("Task " + i + " is completed");
        }));
        // 等待所有任务完成
        executor.awaitTermination(5, TimeUnit.SECONDS);
        // 关闭线程池
        executor.shutdown();
    }
}

,我们使用 Runtime.getRuntime().availableProcessors() 获取当前系统可用的处理器数量,然后创建了一个工作窃取线程池。我们提交了10个任务给线程池,每个任务都会打印出自己的编号和当前运行的线程名,并且在执行完任务后打印出完成信息。由于工作窃取线程池的特性,线程会自动从其他线程窃取任务来执行,因此我们无法预测每个任务会在哪个线程中执行。最后,我们调用了 executor.awaitTermination(5, TimeUnit.SECONDS) 等待所有任务完成,然后调用了 executor.shutdown() 方法来关闭线程池。

这些线程池都是Java中自带的线程池,可以直接使用。不同的线程池适用于不同的场景,根据实际需要选择合适的线程池可以提高应用程序的性能和效率。

封装的线程池的缺点主要有以下几点:

  1. 无法满足特定需求:封装的线程池通常是为了方便使用而设计的,因此可能无法满足某些特定需求。例如,如果需要使用某种特定的线程调度算法,就需要自己实现线程池。

  2. 难以调试:封装的线程池通常会隐藏一些底层细节,这使得在出现问题时难以进行调试。例如,如果线程池中的某个任务出现了异常,我们可能无法知道是哪个任务出了问题。

  3. 可能存在性能问题:封装的线程池通常会提供一些默认配置,这可能无法满足某些高性能的需求。例如,如果需要使用更高效的队列实现,就需要自己实现线程池。

  4. 无界队列可能会引起内存溢出。

    因此,在某些情况下,我们可能需要自己实现线程池来满足特定需求。

目录
相关文章
|
缓存 Java 应用服务中间件
线程池的10个坑你都遇到过吗
日常开发中,为了更好管理线程资源,减少创建线程和销毁线程的资源损耗,我们会使用线程池来执行一些异步任务。但是线程池使用不当,就可能会引发生产事故。大家看完肯定会有帮助的~
245 0
|
2月前
|
消息中间件 Kubernetes Java
记两个有关线程池的小问题
最近小伙伴们找我查的问题里,有两个与线程池相关的,最终都是花了一些时间才揪出原因所在,做一下记录。
45 1
|
7月前
|
缓存 Java
|
6月前
|
存储 缓存 安全
线程池相关详解
线程池相关详解
|
Java
6. 实现简单的线程池
6. 实现简单的线程池
60 0
|
缓存 Java
常用的线程池有哪些?
常用的线程池有哪些?
113 0
|
缓存 Java 调度
线程池的介绍
线程池的介绍
|
Java 数据库连接 容器
关于线程池
关于线程池
97 0
KeyAffinityExecutor 线程池
KeyAffinityExecutor 线程池
|
缓存 NoSQL Java
【线程池】
【线程池】
153 0