Java线程池测试-Spring-空闲线程会进队列

166 阅读2分钟

结论

场景:想尽量减少队列等待(设置线程队列长度为1),直接用线程池处理,并发数量设置的够用(示例设置4个线程),期望在4个请求内,应该都不进入等待队列。

结论证明是错误的:在空余线程够用(小于4)的情况下,依然会先进入队列,并且超出队列长度1后,会发生拒绝。

测试代码

JAVA

package fly.sample.thread;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor 的内部实现
 */
public class SpringThreadPoolTest {
    public static void main(String[] args) throws Exception {
        ApplicationContext factory = new ClassPathXmlApplicationContext("classpath:spring-pool.xml");
        ThreadPoolTaskExecutor executor = factory.getBean("threadPoolTaskExecutor", ThreadPoolTaskExecutor.class);
        List<Future> futures = new ArrayList<>();
        long stime = System.currentTimeMillis();
        System.out.println("第一批");
        for (int i = 1; i <= 10; i++) {
            try {
                Future<Integer> future = executor.submit(new MyCallable(i));
                futures.add(future);
                System.out.println("时间:" + (System.currentTimeMillis() - stime) + " " + "提交成功:" + i + " " + executor.getThreadPoolExecutor());
            } catch (Exception e) {
                System.out.println("时间:" + (System.currentTimeMillis() - stime) + " " + "提交失败:" + i + " " + e.getMessage());
//                e.printStackTrace();
            }

        }
        Thread.sleep(205);
        System.out.println("第二批");
        for (int i = 11; i <= 20; i++) {
            try {
                Future<Integer> future = executor.submit(new MyCallable(i));
                futures.add(future);
                System.out.println("时间:" + (System.currentTimeMillis() - stime) + " " + "提交成功:" + i + " " + executor.getThreadPoolExecutor());
            } catch (Exception e) {
                System.out.println("时间:" + (System.currentTimeMillis() - stime) + " " + "提交失败:" + i + " " + e.getMessage());
//                e.printStackTrace();
            }

        }
        System.out.println(futures.size());
        for (Future future : futures) {
            System.out.println("时间:" + (System.currentTimeMillis() - stime) + " " + "get:" + future.get(105, TimeUnit.MILLISECONDS));
        }
    }


    static class MyCallable implements Callable {
        int result = 0;

        public MyCallable(int result) {
            this.result = result;
        }

        @Override
        public Object call() throws Exception {
            Thread.sleep(100);
            System.out.println("完成:" + result);
            return result;
        }
    }


}

spring-pool.xml

    <bean id="threadPoolTaskExecutor"
          class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" lazy-init="true" scope="singleton">
        <property name="corePoolSize" value="4"/>
        <property name="maxPoolSize" value="4"/>
        <property name="queueCapacity" value="1"/>
        <property name="keepAliveSeconds" value="300"/>
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$AbortPolicy"/>
        </property>
    </bean>

结果分析

结果如下:从现象来看:4个并发+1个队列长度,分两批提交,第二批等待第一批任务全部完成:从现象来看:

  • 第一次提交成功5个,符合预期。
  • 第二次提交成功2个后开始拒绝,不符合预期(13被拒绝了),空余线程是够的,应该是先进队列,如果队列来不及放入执行,就会出现拒绝的情况。
第一批
时间:4 提交成功:1 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
时间:22 提交成功:2 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
时间:23 提交成功:3 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]
时间:23 提交成功:4 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]
时间:23 提交成功:5 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]
时间:23 提交失败:6 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@470f1802
时间:24 提交失败:7 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@3e92efc3
时间:24 提交失败:8 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@72a7c7e0
时间:24 提交失败:9 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@70e8f8e
时间:24 提交失败:10 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@5bd03f44
完成:1
完成:2
完成:3
完成:4
完成:5
第二批
时间:234 提交成功:11 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 1, queued tasks = 0, completed tasks = 5]
时间:235 提交成功:12 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 1, queued tasks = 1, completed tasks = 5]
时间:235 提交失败:13 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 5]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@5a63f509
时间:236 提交成功:14 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 2, queued tasks = 1, completed tasks = 5]
时间:236 提交失败:15 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 2, queued tasks = 1, completed tasks = 5]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@34b7ac2f
时间:236 提交失败:16 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 2, queued tasks = 1, completed tasks = 5]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@4b0b0854
时间:236 提交失败:17 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 2, queued tasks = 1, completed tasks = 5]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@10e41621
时间:236 提交失败:18 Executor [java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 5]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@2667f029
时间:236 提交成功:19 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 3, queued tasks = 1, completed tasks = 5]
时间:236 提交成功:20 java.util.concurrent.ThreadPoolExecutor@418e7838[Running, pool size = 4, active threads = 4, queued tasks = 1, completed tasks = 5]

设置队列长度为0

队列设置0,使用的SynchronousQueue,执行符合预期,结果如下 SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。

第一批
时间:2 提交成功:1 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
时间:17 提交成功:2 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]
时间:17 提交成功:3 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]
时间:17 提交成功:4 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]
时间:17 提交失败:5 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@63021689
时间:18 提交失败:6 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@1622f1b
时间:18 提交失败:7 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@2e4b8173
时间:18 提交失败:8 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@17046283
时间:18 提交失败:9 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@29626d54
时间:18 提交失败:10 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 0]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@6e4784bc
完成:1
完成:2
完成:4
完成:3
第二批
时间:228 提交成功:11 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 1, queued tasks = 0, completed tasks = 4]
时间:229 提交成功:12 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 1, queued tasks = 0, completed tasks = 4]
时间:229 提交成功:13 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4]
时间:229 提交成功:14 java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 3, queued tasks = 0, completed tasks = 4]
时间:229 提交失败:15 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 4]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@e056f20
时间:230 提交失败:16 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 4]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@19bb07ed
时间:230 提交失败:17 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 4]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@353d0772
时间:230 提交失败:18 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 4]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@67a20f67
时间:230 提交失败:19 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 4]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@a9cd3b1
时间:230 提交失败:20 Executor [java.util.concurrent.ThreadPoolExecutor@61230f6a[Running, pool size = 4, active threads = 4, queued tasks = 0, completed tasks = 4]] did not accept task: fly.sample.thread.threadpool.SpringThreadPoolTest$MyCallable@64cd705f

加入线程池源码

只有第一批不进入队列,后面的就算线程空闲,也会先进入队列。 java.util.concurrent.ThreadPoolExecutor- public void execute(Runnable command)

        int c = ctl.get();
        // 判断当前线程数是否小于核心线程数
        if (workerCountOf(c) < corePoolSize) {
            //添加核心线程
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //首先加入线程池
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);