线程池-详细版

druid配置详解

属性 说明 建议值
url 数据库的jdbc连接地址。一般为连接oracle/mysql。示例如下:
mysql : jdbc:mysql://ip:port/dbname?option1&option2&…
oracle : jdbc:oracle:thin:@ip:port:oracle_sid
username 登录数据库的用户名
password 登录数据库的用户密码
initialSize 启动程序时,在连接池中初始化多少个连接 10-50已足够
maxActive 连接池中最多支持多少个活动会话
maxWait 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池 100
没有可用连接,单位毫秒,设置-1时表示无限等待
minEvictableIdleTimeMillis 池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将 见说明部分
回收该连接,要小于防火墙超时设置
net.netfilter.nf_conntrack_tcp_timeout_established的设置
timeBetweenEvictionRunsMillis 检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查
keepAlive 程序没有close连接且空闲时长超过 minEvictableIdleTimeMillis,则会执 true
行validationQuery指定的SQL,以保证该程序连接不会池kill掉,其范围不超
过minIdle指定的连接个数。
minIdle 回收空闲连接时,将保证至少有minIdle个连接. 与initialSize相同
removeAbandoned 要求程序从池中get到连接后, N 秒后必须close,否则druid 会强制回收该 false,当发现程序有未
连接,不管该连接中是活动还是空闲, 以防止进程不会进行close而霸占连接。 正常close连接时设置为true
removeAbandonedTimeout 设置druid 强制回收连接的时限,当程序从池中get到连接开始算起,超过此 应大于业务运行最长时间
值后,druid将强制回收该连接,单位秒。
logAbandoned 当druid强制回收连接后,是否将stack trace 记录到日志中 true
testWhileIdle 当程序请求连接,池在分配连接时,是否先检查该连接是否有效。(高效) true
validationQuery 检查池中的连接是否仍可用的 SQL 语句,drui会连接到数据库执行该SQL, 如果
正常返回,则表示连接可用,否则表示连接不可用
testOnBorrow 程序 申请 连接时,进行连接有效性检查(低效,影响性能) false
testOnReturn 程序 返还 连接时,进行连接有效性检查(低效,影响性能) false
poolPreparedStatements 缓存通过以下两个方法发起的SQL: true
public PreparedStatement prepareStatement(String sql)
public PreparedStatement prepareStatement(String sql,
int resultSetType, int resultSetConcurrency)
maxPoolPrepareStatementPerConnectionSize 每个连接最多缓存多少个SQL 20
filters 这里配置的是插件,常用的插件有: stat,wall,slf4j
监控统计: filter:stat
日志监控: filter:log4j 或者 slf4j
防御SQL注入: filter:wall
connectProperties 连接属性。比如设置一些连接池统计方面的配置。
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
比如设置一些数据库连接属性:

线程池(重点)

线程池(Thread Pool)是一种多线程处理形式,处理过程中将任务提交给线程池,线程池中的线程会异步地执行这些任务。线程池的主要目的是复用线程,减少线程的创建和销毁开销,提高程序的响应速度和吞吐量。

在Java中,java.util.concurrent 包提供了对线程池的支持,包括 ExecutorService 接口和几个实现了该接口的类,如 ThreadPoolExecutorExecutors

优势:

  1. 降低资源消耗:通过复用线程,减少了线程的创建和销毁次数,降低了线程的创建和销毁的开销。
  2. 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以对线程进行统一分配、调优和监控。

JDK中线程池

线程池的体系结构

image-20240217144728987

Java中的线程池是通过Executor框架实现的,该框架用到了Executor , Executors , ExecutorService , ThreadPoolExecutor。

Executors工具类

创建线程池方法

Executors 是 JDK 所提供的线程池 工具类,在该类中提供了很多的静态方法供我们快速的创建线程池对象。

// 创建一个可缓存线程池,可灵活的去创建线程,并且灵活的回收线程,若无可回收,则新建线程。
ExecutorService newCachedThreadPool()
    
// 初始化一个具有固定数量线程的线程池
ExecutorService newFixedThreadPool(int nThreads)
    
// 初始化一个具有一个线程的线程池
ExecutorService newSingleThreadExecutor()
    
// 初始化一个具有一个线程的线程池,支持定时及周期性任务执行
ScheduledExecutorService newSingleThreadScheduledExecutor()

返回值就是线程池对象ExecutorService,ScheduledExecutorService。

ExecutorService中的常见方法

Future<?> submit(Runnable task): 向线程池提交任务
void shutdown(): 关闭线程池

submit():
Future<T> submit(Callable<T> task):提交一个返回值的任务,返回一个 Future 对象,用于表示任务的执行结果。
Future<?> submit(Runnable task):提交一个不带返回值的任务,返回一个 Future 对象。
    
invokeAll():
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks):提交一组任务并等待它们结束,返回一个 Future 列表。
    
invokeAny():
<T> T invokeAny(Collection<? extends Callable<T>> tasks):提交一组任务并返回第一个成功完成的任务的结果,或抛出异常。
    
shutdown():
void shutdown():关闭ExecutorService,不能再接受新任务,但会继续执行已提交的任务。

shutdownNow():
List<Runnable> shutdownNow():立即关闭ExecutorService,尝试停止所有正在执行的任务,返回待执行的任务列表。

isShutdown():
boolean isShutdown():判断ExecutorService是否已关闭。

isTerminated():
boolean isTerminated():判断所有任务是否已完成执行。
    
awaitTermination():
boolean awaitTermination(long timeout, TimeUnit unit):在关闭之后等待所有任务完成,直到超时或任务完成。

execute():
void execute(Runnable command):执行提交的Runnable任务。

1.newCachedThreadPool

无限大线程池:newCachedThreadPool的线程池大小理论上可以是无限的(int的最大值),因为它没有设置最大线程数。当执行第二个任务时,如果第一个任务的线程已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

public class ExecutorsDemo {

    // 演示Executors中的newCachedThreadPool返回的线程池的特点
    public static void main(String[] args) throws InterruptedException {

        // 获取线程池对象
        ExecutorService threadPool = Executors.newCachedThreadPool();

        // 提交任务
        threadPool.submit(() -> {
            System.out.println( Thread.currentThread().getName() + "---执行了任务");
        });

        // 提交任务
        threadPool.submit(() -> {
            System.out.println( Thread.currentThread().getName() + "---执行了任务");
        });

        // 不使用线程池了,还可以将线程池关闭
        threadPool.shutdown();
    }
}

控制台输出结果

pool-1-thread-2---执行了任务
pool-1-thread-1---执行了任务

2.newFixedThreadPool

创建一个具有固定数量线程的线程池

public class ExecutorsDemo {

    // 演示newFixedThreadPool方法所获取到的线程池的特点
    public static void main(String[] args) {

        // 获取线程池对象,初始化一个具有固定数量线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);  // 在该线程池中存在3个线程

        // 提交任务
        for(int x = 0 ; x < 5 ; x++) {
            threadPool.submit( () -> {
                System.out.println(Thread.currentThread().getName() + "----->>>执行了任务" );
            });
        }

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

}

控制台输出结果

pool-1-thread-1----->>>执行了任务
pool-1-thread-2----->>>执行了任务
pool-1-thread-2----->>>执行了任务
pool-1-thread-2----->>>执行了任务
pool-1-thread-3----->>>执行了任务

通过控制台的输出结果,我们可以看到5个任务是通过3个线程进行执行的,说明此线程池中存在三个线程对象。

3.newSingleThreadExecutor

创建一个具有一个线程的线程池

public class ExecutorsDemo {

    // 演示newSingleThreadExecutor方法所获取到的线程池的特点
    public static void main(String[] args) {

        // 获取线程池对象,初始化一个具有一个线程的线程池
        ExecutorService threadPool = Executors.newSingleThreadExecutor();

        // 提交任务
        for(int x = 0 ; x < 5 ; x++) {
            threadPool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "----->>>执行了任务");
            });
        }

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

}

控制台输出结果

pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务
pool-1-thread-1----->>>执行了任务

通过控制台的输出结果,我们可以看到5个任务是通过1个线程进行执行的,说明此线程池中只存在一个线程对象。

4.newSingleThreadScheduledExecutor

支持定时及周期性任务执行

测试1(演示定时执行)

public class ExecutorsDemo {

    // 演示newSingleThreadScheduledExecutor方法所获取到的线程池的特点(支持定时及周期性任务执行)
    public static void main(String[] args) {

        // 获取线程池对象
        ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();

        // 提交任务,10s以后开始执行该任务
        threadPool.schedule( () -> {
            System.out.println(Thread.currentThread().getName() + "---->>>执行了该任务");
        } , 10 , TimeUnit.SECONDS) ;

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

}

测试2(演示周期性执行)

public class ExecutorsDemo {

    // 演示newSingleThreadScheduledExecutor方法所获取到的线程池的特点(支持定时及周期性任务执行)
    public static void main(String[] args) {

        // 获取线程池对象
        ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();

        // 提交任务,10s以后开始第一次执行该任务,然后每隔1秒执行一次
        threadPool.scheduleAtFixedRate( () -> {
            System.out.println(Thread.currentThread().getName() + "---->>>执行了该任务");
        } , 10 ,1, TimeUnit.SECONDS) ;

    }

}

ThreadPoolExecutor创建线程池

ThreadPoolExecutor 是 Java 的 java.util.concurrent 包中的一个重要类,用于管理线程池。线程池是一种设计模式,能够有效地管理和复用线程,减少了线程创建和销毁所带来的性能开销。

ThreadPoolExecutor完整的构造方法:

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

参数说明

corePoolSize:   核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime:  空闲线程最大存活时间,不能小于0
unit:           时间单位
workQueue:      任务队列,不能为null
threadFactory:  创建线程工厂,不能为null      
handler:        任务的拒绝策略,不能为null    

使用自定义线程池

public class ThreadPoolExecutorDemo {

    // 演示基本使用
    public static void main(String[] args) {

        // 通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 3, 60, TimeUnit.SECONDS, 
                                                               new ArrayBlockingQueue<Runnable>(3), 
                                                               Executors.defaultThreadFactory(), 
                                                               new ThreadPoolExecutor.AbortPolicy());

        /**
         * 以上代码表示的意思是:核心线程池中的线程数量最大为1,整个线程池中最多存在3个线程,空闲线程最大的存活时间为60,时间单位为秒,阻塞队列使用的是有界阻塞队列,容量为3, 使用默认的线程工厂; 以及默认的任务处理策略
         */

        // 提交任务
        threadPoolExecutor.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "------>>>执行了任务");
        });

        // 关闭线程池
        threadPoolExecutor.shutdown();

    }
}

BlockingQueue(阻塞队列)

该队列存储的是未被处理的任务,Runnable实例(任务)

在线程池中,阻塞队列用于存储未被处理的任务,没有被处理的任务将会在阻塞队列中进行等待。

BlockingQueue的特征:

1、当生产者线程试图向BlockingQueue中放入元素时,如果队列已满,则线程被阻塞。

2、当消费者线程试图从BlockingQueue中取元素时,如果队列没有元素,则该线程被阻塞。

和阻塞相关的两个方法:

void put(E e) throws InterruptedException;			// 存数据
E take() throws InterruptedException;				// 取出数据,并移除

BlockingQueue的常用实现类:

ArrayBlockingQueue:基于数组实现的有界阻塞队列,有界指的是队列有大小限制,可以put多少元素,取决于数组长度。
    
LinkedBlockingQueue:基于链表实现的无界阻塞队列,并不是绝对的无界(容量:Integer.MAX_VALUE)

SynchronousQueue:只能进行一次put操作。

我们以ArrayBlockingQueue举例来演示一下阻塞队列的使用

public class ArrayBlockingQueueDemo {

    public static void main(String[] args) throws InterruptedException {

        // 创建一个容量为1的阻塞队列
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(1) ;

        // 存储元素
        arrayBlockingQueue.put("java");
        arrayBlockingQueue.put("world");  //因为队列容量为1,当存储第二个元素时,队列会堵塞。

        // 取元素
        System.out.println(arrayBlockingQueue.take());

        System.out.println(arrayBlockingQueue.take());

        // 输出
        System.out.println("程序结束了....");

    }

}

线程池任务的拒绝策略

RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。

ThreadPoolExecutor.AbortPolicy: 		   丢弃任务并抛出RejectedExecutionException异常。是默认的策略。

ThreadPoolExecutor.DiscardPolicy: 		   丢弃任务,但是不抛出异常 这是不推荐的做法。

ThreadPoolExecutor.DiscardOldestPolicy:    抛弃队列中等待最久的任务 然后把当前任务加入队列中。

ThreadPoolExecutor.CallerRunsPolicy:       调用任务的run()方法绕过线程池直接执行。

注:明确线程池最多可执行的任务数 = 队列容量 + 最大线程数

演示ThreadPoolExecutor.DiscardPolicy任务处理策略

public class ThreadPoolExecutorDemo {

    public static void main(String[] args) {

        /**
         * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
         */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;

        // 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
        for(int x = 0 ; x < 5 ; x++) {
            threadPoolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
            });
        }

    }
}

控制台输出结果

pool-1-thread-1---->> 执行了任务
pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务

控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了

线程池自定义拒绝策略

import java.util.concurrent.*;  

public class CustomRejectedExecutionHandlerExample {  

public static void main(String[] args) {  
    // 创建一个线程池  
    ThreadPoolExecutor executor = new ThreadPoolExecutor(  
          2, // 核心线程数  
          4, // 最大线程数  
          1, // 空闲线程存活时间  
          TimeUnit.SECONDS,  
          new ArrayBlockingQueue<>(2), // 工作队列  
          (r,executor)->{// 自定义拒绝策略  
                System.out.println("Task " + r.toString() + " has been rejected.");  
            // 这里可以添加自定义的处理逻辑,比如将任务放到另一个队列中  
         } 
    );  

   executor.shutdown();  
}  

    
    
    // 自定义拒绝策略  
static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {  
       @Override  
       public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {  
         System.out.println("Task " + r.toString() + " has been rejected.");  
            // 这里可以添加自定义的处理逻辑,比如将任务放到另一个队列中  
        }  
    }  
}

通过实现自定义的拒绝策略,Java 的线程池可以更加灵活地处理任务提交的情况,确保在负载高的情况下仍然能够以你希望的方式处理业务逻辑。

线程池关闭

关闭线程池的两个方法:

1、shutdown():停止接收新任务,已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成。
2、shutdownNow():停止接收新任务,原来的任务停止执行。
(1)跟 shutdown() 一样,先停止接收新submit的任务;
(2)忽略队列里等待的任务;
(3)尝试将正在执行的任务interrupt中断;
(4)返回未执行的任务列表;
awaitTermination(60, TimeUnit.SECONDS) 等待所有任务完成,如果超过此时间仍有未完成的任务,则会返回false。

优雅关闭线程池的方式:

1、调用 ExecutorService 的 shutdown() 方法,这将禁止提交新任务,但已经提交的任务将继续执行,直到完成为止。

2、调用 awaitTermination(60, TimeUnit.SECONDS) 方法,等待所有任务完成,如果超过此时间仍有未完成的任务,则会返回false。

3、返回false,则调用ExecutorService 的 shutdownNow() 方法来中断所有正在执行的任务。

executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
	List<Runnable> runnables = executor.shutdownNow();
}

线程池创建方式选择

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。

而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

image-20241017183239828

image-20241017183502984

posted @   CH_song  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示