Java线程池

1 篇文章 0 订阅
1 篇文章 0 订阅

一、Java线程池的四种创建方式


  • newCachedThreadPool

创建一个可缓存线程池,可控制线程最大并发数,可灵活回收空闲线程,超出的线程会在队列中等待。

注意控制任务的数量,防止造成系统瘫痪。


  • newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,不会灵活回收空闲线程,超出的线程会在队列中等待。


  • newScheduledThreadPool

创建一个定长线程池,支持定时周期性任务执行。


  • newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 如果这个线程异常结束,会有另一个取代它。


  • 注意:
    定长线程池的大小最好根据系统资源进行设置如Runtime.getRuntime().availableProcessors()。这个虚拟机可用的处理器数量。可能会在虚拟机的特定调用期间发生变化。 因此,对可用处理器数量敏感的应用程序应偶尔轮询此属性并适当调整其资源使用情况。

  • 导致oom原因:
    LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。

场景:
FixedThreadPool:可重用固定线程数的线程池。(适用于负载比较重的服务器) FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列 该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。

SingleThreadExecutor:只会创建一个线程执行任务。(适用于需要保证顺序执行各个任务;并且在任意时间点,没有多线程活动的场景。) SingleThreadExecutorl也使用无界队列LinkedBlockingQueue作为工作队列 若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。

CachedThreadPool:是一个会根据需要调整线程数量的线程池。(大小无界,适用于执行很多的短期异步任务的小程序,或负载较轻的服务器) CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。 线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。

ScheduledThreadPool:继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。使用DelayQueue作为任务队列。

整个示例演示一下:
直接点进去看一下源码就懂了,其实都是ThreadPoolExecutor,只不过给你定义了一些常用的,用了不同的参数、队列等。

ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService1 = Executors.newFixedThreadPool(3);
ExecutorService executorService2 = Executors.newScheduledThreadPool(3);
ExecutorService executorService3 = Executors.newSingleThreadExecutor();
executorService.execute(() -> System.out.println("abc"));
// 还能这样哦,可以很方便的调用他一些方法
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
scheduledThreadPoolExecutor.execute(() -> System.out.println(666));
//可以很方便的调用他一些方法,比如
public static final ScheduledThreadPoolExecutor RULE_SCHEDULE = new ScheduledThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors() * 2,
            new DefaultThreadFactory("hhh-schedule-"),
            new ThreadPoolExecutor.AbortPolicy() {
                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                    throw new RejectedExecutionException("自己想抛出的异常:" + e);
                }
            }
    );
// 调用,,大概是执行第一个参数,过1s就开始执行,然后每2s执行一次
RULE_SCHEDULE.scheduleAtFixedRate(()-> System.out.println(666), 1, 2, TimeUnit.SECONDS);

有一说一,强烈推荐不要用使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。:
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

3)ScheduledThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

// 额,这是我整一个秒杀工具创建的
// 看看处理器呢
int nThreads = Runtime.getRuntime().availableProcessors();
// 整个名字
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
    .setNameFormat("demo-pool-%d").build();
// 可直接pool.submit(new MyRunnable)
ExecutorService pool = new ThreadPoolExecutor(nThreads, nThreads*2,
    10L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(24), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

看不懂马上介绍下参数哈:

二、线程池参数

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
一、corePoolSize 线程池核心线程大小 即使这些线程处理空闲状态,也不会销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。

二、maximumPoolSize 线程池最大线程数量 如果是CPU密集型应用,则线程池大小设置为N+1----为什么+1?因为可以防止N个线程中有一个线程意外中断或者退出,Cpu不会空闲等待
                                    如果是IO密集型应用,则线程池大小设置为2N+1N是cpu逻辑处理器个数,cpu核数*cpu个数)

三、keepAliveTime 空闲线程存活时间

四、unit 空间线程存活时间单位 keepAliveTime的计量单位

五、workQueue 工作队列

- ArrayBlockingQueue

基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。
当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。
如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

- LinkedBlockingQuene

基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。
由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,
而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

- SynchronousQuene

一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务,否则插入操作一直处于阻塞状态,
吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,
如果线程数量达到maxPoolSize,则执行拒绝策略。

- PriorityBlockingQueue

具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

六、threadFactory 线程工厂 可以用来设定线程名、是否为daemon线程等等

七、handler 拒绝策略
AbortPolicy(抛出一个异常,默认的)
DiscardPolicy(直接丢弃任务)
DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
CallerRunsPolicy(交给线程池调用所在的线程进行处理)

要是你这样也不想用,那你:

三、ThreadPoolTaskExecutor使用

但是工作中最常用的还是spring的ThreadPoolTaskExecutor。
直接配置一下就能用,例如:

@Bean(name = "xxxExecutor")
    public Executor dataSetExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(10);
        // 设置最大线程数
        executor.setMaxPoolSize(50);
        // 设置队列长度
        executor.setQueueCapacity(200);
        // 设置线程存活时间
        executor.setKeepAliveSeconds(600);
        // 设置线程前缀
        executor.setThreadNamePrefix("xxxExecutor-");
        // 设置最长等待时间
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        // 设置丢弃策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 线程池初始化
        executor.initialize();
        return executor;
    }

如何调用呢:

@Resource(name = "xxxExecutor")
private Executor xxxExecutor;
void myMethod(){
	//这不就是匿名内部类直接调吗,你也可以整个类实现Runnable,重写run方法,然后自己设置标志位让线程停止
	xxxExecutor.execute(() -> dosomething);
}

怎么关闭一个线程呢?顺嘴一提:
Thread.stop() 已弃用了哈。
如下,加一个标志位running。或者每个新启的线程都存到map,然后再取出来interrupt哈哈哈


class ControlSubThread2 implements Runnable {

    private Thread worker;
    private final AtomicBoolean running = new AtomicBoolean(false);
    private final AtomicBoolean stopped = new AtomicBoolean(true);
    private int interval;

    // ...
    public ControlSubThread2(int sleepInterval) {
        interval = sleepInterval;
    }
    public void start() {
        worker = new Thread(this);
        worker.start();
    }

    /** 当面临长期阻塞或永远不会彻底终止的风险,直接中断 */
    public void interrupt() {
        running.set(false);
        worker.interrupt();
    }

    public void stop() {
        running.set(false);
    }
    
    boolean isRunning() {
        return running.get();
    }

    boolean isStopped() {
        return stopped.get();
    }

    public void run() {
        running.set(true);
        stopped.set(false);
        while (running.get()) {
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e){
                Thread.currentThread().interrupt();
                System.out.println(
                        "Thread was interrupted, Failed to complete operation");
            }
            // do something
        }
        stopped.set(true);
    }
}

四、处理流程

流程图怎么来的呢,其实就是execute(Runnable command)方法的执行流程(看下面参考的最后一个连接嘛),借用一张图。链接在下方
在这里插入图片描述
https://zhuanlan.zhihu.com/p/346086161

参考:
Java线程池七个参数详解
多线程系列-(七)线程池的基本参数
Java线程池的四种创建方式
线程池介绍及创建线程池的4种方式
为什么要使用线程池;常见的创建线程池的4种方式
java 创建线程的三种方式、创建线程池的四种方式
Java中创建线程池的正确方法
线程池源码解析
How to Kill a Java Thread

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值