iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >javaThreadPoolExecutor线程池拒绝策略避坑
  • 433
分享到

javaThreadPoolExecutor线程池拒绝策略避坑

2024-04-02 19:04:59 433人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录1.场景2. 原因分析3.总结4.思考1.场景 线程池使用DiscardOldestPolicy拒绝策略,阻塞队列使用ArrayBlockingQueue,发现在某些情形下对于得

1.场景

线程池使用DiscardOldestPolicy拒绝策略,阻塞队列使用ArrayBlockingQueue,发现在某些情形下对于得到的Future,调用get()方法当前线程会一直阻塞。

为了便于理解,将实际情景抽象为下面的代码:

ThreadPoolExecutor threadPoolExecutor  = new ThreadPoolExecutor(
        1,
        1,
        1,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(1),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.DiscardOldestPolicy());//新建线程池时核心线程数及最大线程数都设置为1,阻塞队列使用ArrayBlockingQueue,拒绝策略为DiscardOldestPolicy
public void doBusiness(){
    Task task1 = new Task();
    Task task2 = new Task();
    Task task3 = new Task();
    Future<Boolean> future1 = threadPoolExecutor.submit(task1);//当前工作线程为0,会新建一个worker作为工作线程,并执行task1
    Future<Boolean> future2 = threadPoolExecutor.submit(task2);//当前核心线程数已满,会将任务放入阻塞队列
    Future<Boolean> future3 = threadPoolExecutor.submit(task3);
    
    try{
        boolean result = future2.get();
        System.out.println(result);
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
@Test
public void test_doBusiness(){
    doBusiness();//入口
}
private class Task implements Callable<Boolean>{
    @Override
    public Boolean call() throws Exception {
        try {
            Thread.sleep(1000);//模拟业务执行
            return true;
        }catch(Exception e){
            e.printStackTrace();
        }
        return true;
    }
}

2. 原因分析

通过上面代码我们明白了阻塞队列会将task2对应的任务移除,那么为何移除之后调用get()方法线程会一直阻塞呢?

其实Future future2= threadPoolExecutor.submit(task2)实际会调用AbstractExecutorService的submit(Callable task)方法,并且最终返回的future2实际是一个FutureTask类型。

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

因此,我们直接看FutureTask的get()方法

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s &lt;= COMPLETING)
        s = awaitDone(false, 0L);
    return report(s);
}

由于future2已经从阻塞队列中移除,并且从始至终都没有工作线程执行它,即FutureTask的状态一直都为NEW状态,其会进入awaitDone(false,0L)中,接下列我们追踪该方法。

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    Waitnode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)//第一次进for循环时q==null,进入到该分支
            q = new WaitNode();
        else if (!queued)//第二次进for循环时queue为false,则使用CAS将q置为waiters的头结点
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else//将q置为头结点后,最终会进入这里调用park()方法,阻塞当前线程
            LockSupport.park(this);
    }

从上面的代码可以看出调用future2.get()后会一直阻塞在park()方法处,这便是本次问题出现的原因,

3.总结

本次问题出现主要是同时满足了以下几点:

1)使用了有界的阻塞队列ArrayBlockingQueue

2)工作线程达到了线程池配置的最大线程数

3)拒绝策略使用了DiscardOldestPolicy(使用DiscardPolicy也会出现这个问题)

4.思考

我们日常使用线程池提交任务后,如果在任务执行完成之前调用future的get()方法,当前线程会进入阻塞状态,当任务执行完成后,才会将当前线程唤醒,如何从代码上分析该流程?

首先当任务提交到线程池,如果任务当前在阻塞队列中,则FutureTask的状态依然像上面的情况一样,是处于New状态,调用get()方法依然会到达LockSupport.park(this)处,将当前线程阻塞。什么时候才会将当前线程唤醒了?

那就是当存在工作线程Worker目前分配的任务执行完成后,其会去调用Worker类的getTask()方法从阻塞队列中拿到该任务,并执行该任务的run()方法,下面是FutureTask的run()方法

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);//如果任务执行成功,则调用set(V result)方法
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

其会在执行成功后,调用set(V result)方法

protected void set(V v) {
    if (UNSAFE.compareAndSwapint(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();//
    }
}

然后将FutureTask状态置为NORMAL(FutureTask的状态要和ThreadPoolExecutor的状态区分开),接着调用finishCompletion()方法

private void finishCompletion() {
    // assert state > COMPLETING;
    for (WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                Thread t = q.thread;//q在await()方法中设置的,其值为调用get()方法的线程
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);//唤醒该线程
                }
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help GC
                q = next;
            }
            break;
        }
    }
    done();//熟悉的钩子方法
    callable = null;        // to reduce footprint
}

在finishCompletion中唤起因get()而阻塞的线程。

以上就是java ThreadPoolExecutor线程池拒绝策略避坑的详细内容,更多关于java ThreadPoolExecutor的资料请关注编程网其它相关文章!

--结束END--

本文标题: javaThreadPoolExecutor线程池拒绝策略避坑

本文链接: https://www.lsjlt.com/news/165181.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • javaThreadPoolExecutor线程池拒绝策略避坑
    目录1.场景2. 原因分析3.总结4.思考1.场景 线程池使用DiscardOldestPolicy拒绝策略,阻塞队列使用ArrayBlockingQueue,发现在某些情形下对于得...
    99+
    2022-11-13
  • Java线程池拒绝策略是什么
    本篇内容介绍了“Java线程池拒绝策略是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、CallerRunsPolicy(调用者运行策...
    99+
    2023-06-15
  • Java线程池有哪些拒绝策略
    这期内容当中小编将会给大家带来有关Java线程池有哪些拒绝策略,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。池化设计思想池话设计应该不是一个新名词。我们常见的如java线程池、jdbc连接池、redis连...
    99+
    2023-06-16
  • Java线程池ThreadPoolExecutor拒绝策略有哪些
    本篇内容介绍了“Java线程池ThreadPoolExecutor拒绝策略有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!池化设计思想池...
    99+
    2023-06-16
  • java线程池ThreadPoolExecutor的拒绝策略有哪些
    本篇内容主要讲解“java线程池ThreadPoolExecutor的拒绝策略有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“java线程池ThreadPoolExecutor的拒绝策略有哪...
    99+
    2023-06-21
  • java中线程池的拒绝策略有哪些
    本篇文章为大家展示了java中线程池的拒绝策略有哪些,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象编程语言的代表,实现了面向对...
    99+
    2023-06-14
  • Java线程池的四种拒绝策略详解
    目录预先配置配置线程池。创建线程任务拒绝策略一:AbortPolicy拒绝策略二:CallerRunsPolicy拒绝策略三:DiscardPolicy拒绝策略四:DiscardOl...
    99+
    2022-11-13
  • java 线程池如何执行策略又拒绝哪些策略
    目录线程池执行流程线程池拒绝策略DiscardPolicy拒绝策略AbortPolicy拒绝策略自定义拒绝策略总结前言: 聊到线程池就一定会聊到线程池的执行流程,也就是当有一个任务进...
    99+
    2022-11-13
  • 详解什么是Java线程池的拒绝策略?
    目录一、拒绝策略1.1 AbortPolicy(默认拒绝策略)1.2 CallerRunsPolicy(使用调用线程池的线程来执行任务 )1.3 DiscardPolicy (忽略新...
    99+
    2022-11-12
  • java线程池ThreadPoolExecutor的八种拒绝策略示例详解
    目录池化设计思想线程池触发拒绝策略的时机JDK内置4种线程池拒绝策略拒绝策略接口定义CallerRunsPolicy(调用者运行策略)AbortPolicy(中止策略)Discard...
    99+
    2022-11-12
  • Spring Boot配置线程池拒绝策略的场景分析(妥善处理好溢出的任务)
    目录场景重现配置拒绝策略代码示例通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务、为异步任务配置线程池、使用多个线程池隔离不同的异步...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作