线程的创建以及线程池配置使用

188 阅读6分钟

嗨 大伙儿好呀,好久不更新,最近太忙了~ 效能中台最近新搭建了一套针对灵活用工和人才追踪的系统,由于是从0到一,在搭建线程池的时候又回顾了一下线程的创建和线程池的创建,个人知识笔记,欢迎批评和点赞~

前言

创建线程的四种方式:

  • 继承thread类: 1、继承Thread类 2、重写run方法(run是子线程的入口点) 3、通过start方法启动线程
public class ThreadAli extends Thread {
   @Override
   public void run() {
       System.out.println("threadAli 继承thread类");
   }
}

使用:
ThreadAli threadAli = new ThreadAli();
threadAli.setName("ali");
threadAli.start();
好处:继承了thread的多线程功能 劣势:因为是继承而来,在oop上单继承不容易抽象,比较局限,往上看thread也是实现runnable接口而来
  • 实现runnable接口:1、实现runnable 2、重写run方法 3、传入目标对象+Thread对象.start()
以下为了方便我直接使用Lambda来创建了
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("threadAlili runnable实现");
    }
}).start();
这种方法也是我们较继承Thread来说使用最多的方法,这样避免了单继承的局限性,更方便抽象。
  • 实现callable接口接口 1、实现collable接口 2、重写call方法 3、构造FutureTask对象 4、传入目标对象+Thread对象.start()执行任务 在使用之前我们需要先看下callable说明:
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result 
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

看源码知道call方法会返回一个结果,,因为callable是带返回结果的call方法,而我们无法直接去使用(Thread类里并没有关于collable接口的方法),我们需要有一个中介去帮我们执行任务,并在执行完告诉我们执行的结果,这时候就引出了另一个类,FutureTask,看该类的说明:

/**

* A cancellable asynchronous computation. This class provides a base

* implementation of {@link Future}, with methods to start and cancel

* a computation, query to see if the computation is complete, and

* retrieve the result of the computation. The result can only be

* retrieved when the computation has completed; the {@code get}

* methods will block if the computation has not yet completed. Once

* the computation has completed, the computation cannot be restarted

* or cancelled (unless the computation is invoked using

* {@link #runAndReset}).

*

* <p>A {@code FutureTask} can be used to wrap a {@link Callable} or

* {@link Runnable} object. Because {@code FutureTask} implements

* {@code Runnable}, a {@code FutureTask} can be submitted to an

* {@link Executor} for execution.

*

* <p>In addition to serving as a standalone class, this class provides

* {@code protected} functionality that may be useful when creating

* customized task classes.

*

* @since 1.5

* @author Doug Lea

* @param <V> The result type returned by this FutureTask's {@code get} methods

*/

public class FutureTask<V> implements RunnableFuture<V>

大概意思就是提供一个异步的任务编排计算,用来包装可调用对象或可运行对象。因为FutureTask实现了Runnable,所以FutureTask可以提交给Executor执行,因为实现RunnableFuture接口,而RunnableFuture又继承了runnable接口所以Thread类可以传递futureaTask进行异步任务处理

        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return Thread.currentThread().getName() + ":祝大家大展宏🐰";
            }
        });
        new Thread(futureTask).start();
        String result = futureTask.get();

通过futureTask我们可以拿到任务编排之后的结果,具体的api使用,大家感兴趣的可以看一下源码

  • 线程池:和数据库连接池、redis连接池等一样,对于调度成本高的都使用了池子提高性能,减少资源浪费,线程也一样,通过线程池我们可以减少创建线程时时间上的浪费,以及对线程的管理更加方便、集中,任务执行更快。线程池的创建方法:
    1. Executors创建:这是juc包下的一个线程池工具类,里面给我们预先定义了一些线程池,有很多,感兴趣的同学可以看下源码,阿里手册规定线程池不允许使用Executors去创建,而应该使用ThreadPoolExecutor方式自定义去创建线程池,所以除了SingleThreadExecutor,其他场景都是去配置线程池使用~,建议大家考下阿里手册,确实有很多值的我们学习的细节。
    2. 自定义线程池ThreadPoolExecutor:在实际项目管理里,我们把线程池作为一个配置类交给spring去做一个管理,实现方式有两种:一种是java自带的ThreadPoolExecutor,另一种是spring的threadPoolTaskExecutor的,原理一样,只是对ThreadPoolExecutor进行了一个封装~ 我们看下实现:
java自带的方式:
@Bean("threadPoolExecutor")
public ThreadPoolExecutor getThreadPoolExecutor() {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(50,
            100,
            0L,
            TimeUnit.MICROSECONDS,
            new LinkedBlockingQueue<>(100),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.DiscardPolicy()
    );
    return threadPoolExecutor;
}

spring的方式:
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor getThreadPoolTaskExceutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    // 设置核心线程数
    threadPoolTaskExecutor.setCorePoolSize(6);
    // 设置最大线程数
    threadPoolTaskExecutor.setMaxPoolSize(12);
    // 设置线程前缀
    threadPoolTaskExecutor.setThreadNamePrefix("thread-pool-qyxn");
    // 设置队列大小
    threadPoolTaskExecutor.setQueueCapacity(30);
    // 设置拒绝策略
    threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    return threadPoolTaskExecutor;
}

spring的话,提供了一些便携操作比如自定义线程池的名字,比java自带的起名字要方便很多,还有队列的选型等等,大家可以根据自己需要选择,最核心的还是参数设置以及运行原理,结合自己的业务场景去调整

参数  1.corePoolSize: 核心线程数
     2.maxPoolSize:最大线程数
     3.过期时间:当线程数大于核心线程数时候,空余线程等待任务的最长时间,过了改时间之后被销毁
     4.过期时间单位
     5.阻塞队列,阻塞队列分为多种:
         1)ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
         2)LinkedBlockingQueue :由链表结构组成的有界阻塞队列
         3)DelayQueue: 延时队列一个实现PriorityBlockingQueue实现延迟获取的无界队列,在创建元素时,可以指定多久才能从队列中获取当前元素
         4)SynchronousQueue:单个队列,只有一个任务消费完才有新的任务生产进来
         5)
     6.线程创建工厂,使用默认的就好
     7.当核心线程满了,且最大线程也满了,队列也满了,对于新来的任务所指定的拒绝策略:
         分为四种,分别是:
         1)DiscardOldestPolicy策略:丢弃任务队列中最早添加的任务,并尝试提交当前任务;
         2)CallerRunsPolicy策略:调用主线程执行被拒绝的任务,这提供了一种简单的反馈控制机制,将降低新任务的提交速度。
         3)DiscardPolicy策略:默默丢弃无法处理的任务,不予任何处理。
         4)AbortPolicy策略:直接抛出异常,阻止系统正常工作
     8.工作原理:
         当有任务来的时候,如果当前线程数小于核心线程数,那么就立即创建线程来执行当前任务,如果当前线程数大于等于核心线程数那么就将
         该任务放到阻塞队列中等待,等核心线程数空余出来就执行阻塞队列中的任务,如果阻塞队列满了,这时候会有两种情况:1当前线程数大于等于
         最大线程数,则执行对应的阻塞策略(例如返回给调用者、放弃等等)2当前线程数大于等于核心线程数且小于最大线程数,则开启救急线程来处理
         阻塞队列中的任务,并把任务放入阻塞队列,当除了核心线程以外的其他线程空闲时间到达设置的阀值之后就会把这些空闲线程销毁。

在使用的时候,按照我们的使用场景,定义不同名字的线程池,这样在排查问题的时候方便出错时回溯,在使用注解调用异步方法的时候,也要根据场景指定线程池名字,

image.png 暂时就这么多吧,最近回顾挺多的,也走了很多坑,欢迎大家批评和讨论~