深入浅出线程池

news2024/11/30 12:30:32

 一、线程

1、什么是线程

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际 运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线 程并行执行不同的任务。

2、如何创建线程

2.1、JAVA中创建线程

/**
 * 继承Thread类,重写run方法
 */
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("myThread..." + Thread.currentThread().getName());
} }

/**
 * 实现Runnable接口,实现run方法 
 */
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("MyRunnable..." + Thread.currentThread().getName());
} }

/**
 * 实现Callable接口,指定返回类型,实现call方法
 */
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "MyCallable..." + Thread.currentThread().getName();
} }

2.2、测试一下

public static void main(String[] args) throws Exception {
    MyThread thread = new MyThread();
    thread.run();   //myThread...main
    thread.start(); //myThread...Thread-0
    
    MyRunnable myRunnable = new MyRunnable();
    Thread thread1 = new Thread(myRunnable);
    myRunnable.run();   //MyRunnable...main
    thread1.start();    //MyRunnable...Thread-1
    
    MyCallable myCallable = new MyCallable();
    FutureTask<String> futureTask = new FutureTask<>(myCallable);
    Thread thread2 = new Thread(futureTask);
    thread2.start();
    System.out.println(myCallable.call());  //MyCallable...main
    System.out.println(futureTask.get());   //MyCallable...Thread-2

} 

2.3、问题

既然我们创建了线程,那为何我们直接调用方法和我们调用start()方法的结果不同?new Thread() 是否真实创建了线程?

2.4、问题分析

我们直接调用方法,可以看到是执行的主线程,而调用start()方法就是开启了新线程,那说明new Thread()并没有创建线程,而是在start()中创建了线程。

那我们看下Thread类start()方法:

class Thread implements Runnable { //Thread类实现了Runnalbe接口,实现了run()方法 
    
    private Runnable target;

    public synchronized void start() {
        ...

        boolean started = false;
        try {
            start0(); //可以看到,start()方法真实的调用时start0()方法 
            started = true;
        } finally {
            ...     
        } 
    }
    
    private native void start0();  //start0()是一个native方法,由JVM调用底层操作系统,开启一个线程,由操作系统过统一调度 

    @Override
    public void run() {
        if (target != null) {
             target.run(); //操作系统在执行新开启的线程时,回调Runnable接口的run()方法,执行我们预设的线程任务

        } 
     } 
} 

2.5、总结

  1. JAVA不能直接创建线程执行任务,而是通过创建Thread对象调用操作系统开启线程,在由操作系 统回调Runnable接口的run()方法执行任务;

  2. 实现Runnable的方式,将线程实际要执行的回调任务单独提出来了,实现线程的启动与回调任务 解耦;

  3. 实现Callable的方式,通过Future模式不但将线程的启动与回调任务解耦,而且可以在执行完成后 获取到执行的结果;

二、多线程

1、什么是多线程

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。同一个线程只 能处理完一个任务在处理下一个任务,有时我们需要多个任务同时处理,这时,我们就需要创建多 个线程来同时处理任务。

2、多线程有什么好处

2.1、串行处理

public static void main(String[] args) throws Exception {
    System.out.println("start...");
    long start = System.currentTimeMillis();
    for (int i = 0; i < 5; i++) {
        Thread.sleep(2000);  //每个任务执行2秒 
        System.out.println("task done..."); //处理执行结果
    }
    long end = System.currentTimeMillis();
    System.out.println("end...,time = "  + (end - start));
}
//执行结果
start...
task done...
task done...
task done...
task done...
task done... end...,time = 10043

2.2、并行处理

public static void main(String[] args) throws Exception {
    System.out.println("start...");
    long start = System.currentTimeMillis();
    List<Future> list = new ArrayList<>();

    for (int i = 0; i < 5; i++) {
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000); //每个任务执行2秒 
                return "task done...";
            }

        };
        FutureTask task = new FutureTask(callable);
        list.add(task);
        new Thread(task).start();

    }
    
    list.forEach(future -> {
        try { 
            System.out.println(future.get()); //处理执行结果 } catch (Exception e) {
         } 
    });
    
    long end = System.currentTimeMillis();
    System.out.println("end...,time = " + (end - start));

} 
//执行结果
 start...
 task done...
 task done...
 task done...
 task done...
 task done... end...,time = 2005 

2.3、总结

  1. 多线程可以把一个任务拆分为几个子任务,多个子任务可以并发执行,每一个子任务就是一个线程。

  2. 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统 的效率。

2.4、多线程的问题

上面示例中我们可以看到,如果每来一个任务,我们就创建一个线程,有很多任务的情况下,我们 会创建大量的线程,可能会导致系统资源的耗尽。同时,我们知道线程的执行是需要抢占CPU资源 的,那如果有太多的线程,就会导致大量时间用在线程切换的开销上。

再有,每来一个任务都需要创建一个线程,而创建一个线程需要调用操作系统底层方法,开销较 大,而线程执行完成后就被回收了。在需要大量线程的时候,创建线程的时间就花费不少了。

三、线程池

1、如何设计一个线程池

由于多线程的开发存在上述的一些问题,那我们是否可以设计一个东西来避免这些问题呢?当然可以! 线程池就是为了解决这些问题而生的。那我们该如何设计一个线程池来解决这些问题呢?或者说,一个线程池该具备什么样的功能?

1.1、线程池基本功能

  1. 多线程会创建大量的线程耗尽资源,那线程池应该对线程数量有所限制,可以保证不会耗尽系统资 源;

  2. 每次创建新的线程会增加创建时的开销,那线程池应该减少线程的创建,尽量复用已创建好的线 程;

1.2、线程池面临问题

  1. 我们知道线程在执行完自己的任务后就会被回收,那我们如何复用线程?

  2. 我们指定了线程的最大数量,当任务数超出线程数时,我们该如何处理?

1.3、创新源于生活

先假设一个场景:假设我们是一个物流公司的管理人员,要配送的货物就是我们的任务,货车就是 我们配送工具,我们当然不能有多少货物就准备多少货车。那当顾客源源不断的将货物交给我们配 送,我们该如何管理才能让公司经营的最好呢?

  1. 最开始货物来的时候,我们还没有货车,每批要运输的货物我们都要购买一辆车来运输;

  2. 当货车运输完成后,暂时还没有下一批货物到达,那货车就在仓库停着,等有货物来了立马就可以 运输;

  3. 当我们有了一定数量的车后,我们认为已经够用了,那后面就不再买车了,这时要是由新的货物来 了,我们就会让货物先放仓库,等有车回来在配送;

  4. 当618大促来袭,要配送的货物太多,车都在路上,仓库也都放满了,那怎么办呢?我们就选择临 时租一些车来帮忙配送,提高配送的效率;

  5. 但是货物还是太多,我们增加了临时的货车,依旧配送不过来,那这时我们就没办法了,只能让发 货的客户排队等候或者干脆不接受了;

  6. 大促圆满完成后,累计的货物已经配送完成了,为了降低成本,我们就将临时租的车都还了;

1.4、技术源于创新

基于上述场景,物流公司就是我们的线程池、货物就是我们的线程任务、货车就是我们的线程。我 们如何设计公司的管理货车的流程,就应该如何设计线程池管理线程的流程。

  1. 当任务进来我们还没有线程时,我们就该创建线程执行任务;

  2. 当线程任务执行完成后,线程不释放,等着下一个任务进来后接着执行;

  3. 当创建的线程数量达到一定量后,新来的任务我们存起来等待空闲线程执行,这就要求线程池有个 存任务的容器;

  4. 当容器存满后,我们需要增加一些临时的线程来提高处理效率;

  5. 当增加临时线程后依旧处理不了的任务,那就应该将此任务拒绝;

  6. 当所有任务执行完成后,就应该将临时的线程释放掉,以免增加不必要的开销;

2、线程池具体分析

上文中,我们讲了该如何设计一个线程池,下面我们看看大神是如何设计的;

2.1、 JAVA中的线程池是如何设计的

2.1.1、 线程池设计

看下线程池中的属性,了解线程池的设计。

public class ThreadPoolExecutor extends AbstractExecutorService {

    //线程池的打包控制状态,用高3位来表示线程池的运行状态,低29位来表示线程池中工作线程的数量 
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 
    
    //值为29,用来表示偏移量
     private static final int COUNT_BITS = Integer.SIZE - 3; 

    //线程池的最大容量
     private static final int CAPACITY = (1 << COUNT_BITS) - 1; 

    //线程池的运行状态,总共有5个状态,用高3位来表示 
    private static final int RUNNING = -1 << COUNT_BITS;  //接受新任务并处理阻塞队列中的任务 

    private static final int SHUTDOWN = 0 << COUNT_BITS;  //不接受新任务但会处理阻塞队列中的任务  

    private static final int STOP = 1 << COUNT_BITS;  //不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务

    private static final int TIDYING = 2 << COUNT_BITS;  //所有任务都已终止, 工作线程数量为0,即将要执行terminated()钩子方法 

    private static final int TERMINATED =  3 << COUNT_BITS;  // terminated()方法已经执行结束

    //任务缓存队列,用来存放等待执行的任务
    private final BlockingQueue<Runnable> workQueue; 

    //全局锁,对线程池状态等属性修改时需要使用这个锁
    private final ReentrantLock mainLock = new ReentrantLock(); 

    //线程池中工作线程的集合,访问和修改需要持有全局锁
    private final HashSet<Worker> workers = new HashSet<Worker>(); 

    // 终止条件
    private final Condition termination = mainLock.newCondition(); 

    //线程池中曾经出现过的最大线程数 
    private int largestPoolSize; 
    
    //已完成任务的数量
    private long completedTaskCount; 
    
    //线程工厂
    private volatile ThreadFactory threadFactory; 
    
    //任务拒绝策略
    private volatile RejectedExecutionHandler handler; 

    //线程存活时间
    private volatile long keepAliveTime; 

    //是否允许核心线程超时
    private volatile boolean allowCoreThreadTimeOut; 

    //核心池大小,若allowCoreThreadTimeOut被设置,核心线程全部空闲超时被回收的情况下会为0 
    private volatile int corePoolSize; 

    //最大池大小,不得超过CAPACITY
    private volatile int maximumPoolSize; 
    
    //默认的任务拒绝策略
    private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

    //运行权限相关
    private static final RuntimePermission shutdownPerm = 
        new RuntimePermission("modifyThread");

    ... 
} 

小结一下:以上线程池的设计可以看出,线程池的功能还是很完善的。

  1. 提供了线程创建、数量及存活时间等的管理;

  2. 提供了线程池状态流转的管理;

  3. 提供了任务缓存的各种容器;

  4. 提供了多余任务的处理机制;

  5. 提供了简单的统计功能;

2.1.2、线程池构造函数
//构造函数
 public ThreadPoolExecutor(int corePoolSize, //核心线程数 
                           int maximumPoolSize, //最大允许线程数 
                           long keepAliveTime, //线程存活时间 
                           TimeUnit unit, //存活时间单位 
                           BlockingQueue<Runnable> workQueue, //任务缓存队列
                           ThreadFactory threadFactory, //线程工厂 
                           RejectedExecutionHandler handler) { //拒绝策略 
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
        
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
        
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
} 

小结一下:

  1. 构造函数告诉了我们可以怎样去适用线程池,线程池的哪些特性是我们可以控制的;
2.1.3、线程池执行

2.1.3.1、提交任务方法

• public void execute(Runnable command);

• Future<?> submit(Runnable task);

• Future submit(Runnable task, T result);

• Future submit(Callable task);

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
}

可以看到submit方法的底层调用的也是execute方法,所以我们这里只分析execute方法;

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        int c = ctl.get();
        //第一步:创建核心线程
        if (workerCountOf(c) < corePoolSize) {  //worker数量小于corePoolSize
            if (addWorker(command, true))       //创建worker
                return;
            c = ctl.get();
        }
        //第二步:加入缓存队列
        if (isRunning(c) && workQueue.offer(command)) { //线程池处于RUNNING状态,将任务加入workQueue任务缓存队列
            int recheck = ctl.get();    
            if (! isRunning(recheck) && remove(command))    //双重检查,若线程池状态关闭了,移除任务
                reject(command);
            else if (workerCountOf(recheck) == 0)       //线程池状态正常,但是没有线程了,创建worker
                addWorker(null, false);
        }
        //第三步:创建临时线程
        else if (!addWorker(command, false))
            reject(command);
    }

小结一下:execute()方法主要功能:

  1. 核心线程数量不足就创建核心线程;

  2. 核心线程满了就加入缓存队列;

  3. 缓存队列满了就增加非核心线程;

  4. 非核心线程也满了就拒绝任务;

2.1.3.2、创建线程

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
​
            //等价于:rs>=SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())
            //线程池已关闭,并且无需执行缓存队列中的任务,则不创建
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
​
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))  //CAS增加线程数
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
​
        //上面的流程走完,就可以真实开始创建线程了
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);  //这里创建了线程
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
​
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);     //这里将线程加入到线程池中
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();      //添加成功,启动线程
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);     //添加线程失败操作
        }
        return workerStarted;
    }

小结:addWorker()方法主要功能;

  1. 增加线程数;

  2. 创建线程Worker实例加入线程池;

  3. 加入完成开启线程;

  4. 启动失败则回滚增加流程;

2.1.3.3、工作线程的实现

    private final class Worker  //Worker类是ThreadPoolExecutor的内部类
        extends AbstractQueuedSynchronizer  
        implements Runnable
    {
        
        final Thread thread;    //持有实际线程
        Runnable firstTask;     //worker所对应的第一个任务,可能为空
        volatile long completedTasks;   //记录执行任务数
​
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
        
        public void run() {
            runWorker(this);    //当前线程调用ThreadPoolExecutor中的runWorker方法,在这里实现的线程复用
        }
​
        ...继承AQS,实现了不可重入锁...
    }

小结:工作线程Worker类主要功能;

  1. 此类持有一个工作线程,不断处理拿到的新任务,持有的线程即为可复用的线程;

  2. 此类可看作一个适配类,在run()方法中真实调用runWorker()方法不断获取新任务,完成线程复用;

2.1.3.4、线程的复用

    final void runWorker(Worker w) {    //ThreadPoolExecutor中的runWorker方法,在这里实现的线程复用
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;   //标识线程是否异常终止
        try {
            while (task != null || (task = getTask()) != null) {    //这里会不断从任务队列获取任务并执行
                w.lock();
                
                //线程是否需要中断
                if ((runStateAtLeast(ctl.get(), STOP) ||    
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);    //执行任务前的Hook方法,可自定义
                    Throwable thrown = null;
                    try {
                        task.run();             //执行实际的任务
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown); //执行任务后的Hook方法,可自定义
                    }
                } finally {
                    task = null;    //执行完成后,将当前线程中的任务制空,准备执行下一个任务
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);    //线程执行完成后的清理工作
        }
    }

小结:runWorker()方法主要功能;

  1. 循环从缓存队列中获取新的任务,直到没有任务为止;

  2. 使用worker持有的线程真实执行任务;

  3. 任务都执行完成后的清理工作;

2.1.3.5、队列中获取待执行任务

    private Runnable getTask() {
        boolean timedOut = false;   //标识当前线程是否超时未能获取到task对象
​
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
​
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
​
            int wc = workerCountOf(c);
​
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
​
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))      //若线程存活时间超时,则CAS减去线程数量
                    return null;
                continue;
            }
​
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :   //允许超时回收则阻塞等待
                    workQueue.take();                                   //不允许则直接获取,没有就返回null
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

小结:getTask()方法主要功能;

  1. 实际在缓存队列中获取待执行的任务;

  2. 在这里管理线程是否要阻塞等待,控制线程的数量;

2.1.3.6、清理工作

  private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();
​
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);          //移除执行完成的线程
        } finally {
            mainLock.unlock();
        }
​
        tryTerminate();     //每次回收完一个线程后都尝试终止线程池
​
        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {    //到这里说明线程池没有终止
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);     //异常终止线程的话,需要在常见一个线程
        }
    }

小结:processWorkerExit()方法主要功能;

  1. 真实完成线程池线程的回收;

  2. 调用尝试终止线程池;

  3. 保证线程池正常运行;

2.1.3.7、尝试终止线程池

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            
            //若线程池正在执行、线程池已终止、线程池还需要执行缓存队列中的任务时,返回
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;
                
            //执行到这里,线程池为SHUTDOWN且无待执行任务 或 STOP 状态
            if (workerCountOf(c) != 0) {
                interruptIdleWorkers(ONLY_ONE);     //只中断一个线程
                return;
            }
​
            //执行到这里,线程池已经没有可用线程了,可以终止了
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {  //CAS设置线程池终止
                    try {
                        terminated();   //执行钩子方法
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));  //这里将线程池设为终态
                        termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

小结:tryTerminate()方法主要功能;

  1. 实际尝试终止线程池;

  2. 终止成功则调用钩子方法,并且将线程池置为终态。

2.2、JAVA线程池总结

以上通过对JAVA线程池的具体分析我们可以看出,虽然流程看似复杂,但其实有很多内容都是状态重复校验、线程安全的保证等内容,其主要的功能与我们前面所提出的设计功能一致,只是额外增加了一些扩展,下面我们简单整理下线程池的功能;

2.2.1、主要功能

  1. 线程数量及存活时间的管理;

  2. 待处理任务的存储功能;

  3. 线程复用机制功能;

  4. 任务超量的拒绝功能;

2.2.2、扩展功能

  1. 简单的执行结果统计功能;

  2. 提供线程执行异常处理机制;

  3. 执行前后处理流程自定义;

  4. 提供线程创建方式的自定义;

2.2.3、流程总结

以上通过对JAVA线程池任务提交流程的分析我们可以看出,线程池执行的简单流程如下图所示;

2.3、JAVA线程池使用

线程池基本使用验证上述流程:

   public static void main(String[] args) throws Exception {
        
        //创建线程池
       ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
               5, 10, 100, TimeUnit.SECONDS, new ArrayBlockingQueue(5));
        
        //加入4个任务,小于核心线程,应该只有4个核心线程,队列为0
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new MyRunnable());
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 4
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 0
        
        //再加4个任务,超过核心线程,但是没有超过核心线程 + 缓存队列容量,应该5个核心线程,队列为3
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new MyRunnable());
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 5
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 3
        
        //再加4个任务,队列满了,应该5个热核心线程,队列5个,非核心线程2个
        for (int i = 0; i < 4; i++) {
            threadPoolExecutor.submit(new MyRunnable());
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 7
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 5
        
        //再加4个任务,核心线程满了,应该5个热核心线程,队列5个,非核心线程5个,最后一个拒绝
        for (int i = 0; i < 4; i++) {
            try {
                threadPoolExecutor.submit(new MyRunnable());
            } catch (Exception e) {
                e.printStackTrace();    //java.util.concurrent.RejectedExecutionException
            }
        }
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 10
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 5
        System.out.println(threadPoolExecutor.getTaskCount());  //共执行15个任务
        
        //执行完成,休眠15秒,非核心线程释放,应该5个核心线程,队列为0
        Thread.sleep(1500);
        System.out.println("worker count = " + threadPoolExecutor.getPoolSize());   //worker count = 5
        System.out.println("queue size = " + threadPoolExecutor.getQueue().size()); //queue size = 0
        
        //关闭线程池
        threadPoolExecutor.shutdown();
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1054828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023-10-01 LeetCode每日一题(买卖股票的最佳时机)

2023-10-01每日一题 一、题目编号 121. 买卖股票的最佳时机二、题目链接 点击跳转到题目位置 三、题目描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#xff0c;并选择在 未来的某一…

Python Appium 安卓自动化测试 基本使用 - Phone Spider

Python Appium 安卓自动化测试 基本使用 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 Python Appium 安卓自动化测试 基本使用前言一、环境安装1.1 Python Pip…

【接口技术】输入输出接口习题

1&#xff1a;图1所示电路PC/XT系统板上的接口控制电路的端口地址译码电路。写出8237A&#xff0c;8259A&#xff0c;8253&#xff0c;8255A的端口地址范围 解答&#xff1a; 【G1A非】和【G2B非】为低电平有效&#xff0c;因此A80&#xff0c;A90 74ls138中&#xff0c;是按…

python代码混淆与代码打包

0x00 背景 自己写的项目&#xff0c;又想保护源码&#xff0c;自己做个混淆是最方便的了。 0x01 实践 这里使用开源工具 GitHub - astrand/pyobfuscate: pyobfuscate&#xff0c;虽然git上才500多star&#xff0c;但是很好用。它的使用场景是混淆单个py文件。很多事物有开始就…

六、vpp 流表

草稿&#xff01;&#xff01;&#xff01; vpp node其实就是三个部分 1、plugin init 2、set command 3、function 实现功能&#xff0c;比如这里的流表 这里的function函数有个返回值&#xff0c;根据返回值决定下一个节点走哪里 flowtable_getinfo这里处理函数返回2 &#…

Nodejs沙箱逃逸

一、基本概念 JavaScript和Nodejs之间有什么区别 JavaScript用在浏览器前端&#xff0c;后来将Chrome中的v8引擎单独拿出来为JavaScript单独开发了一个运行环境&#xff0c;因此JavaScript也可以作为一门后端语言&#xff0c;写在后端&#xff08;服务端&#xff09;的JavaScr…

什么是博弈论?

什么是博弈&#xff1f;字面描述中&#xff0c;博弈由两个字构成&#xff1a;博 和 弈。博弈是一种双方&#xff08;多方&#xff09;的对抗&#xff08;比赛&#xff09;&#xff0c;对抗总是在一定的规则下进行&#xff0c;参与者必然会考虑应用相应的策略&#xff08;计谋&a…

防火墙基础之H3C防火墙分支与分支之间双向地址转换

分支与分支之间双向地址转换 原理概述&#xff1a; 防火墙&#xff08;英语&#xff1a;Firewall&#xff09;技术是通过有机结合各类用于安全管理​与筛选的软件和硬件​设备&#xff0c;帮助计算机网络于其内、外网之间构建一道相对隔绝的保护屏障&#xff0c;以保护用户资…

leetCode 376.摆动序列 动态规划 + 图解 + 状态转移

376. 摆动序列 - 力扣&#xff08;LeetCode&#xff09; 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 。第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 例如…

SpringBoot整合RocketMQ笔记

SpringBoot版本为2.3.12.Release RocketMQ对比kafka 学习链接 https://zhuanlan.zhihu.com/p/335216381 代码实战 https://www.cnblogs.com/RedOrange/p/17401238.html Centos安装rocketmq https://blog.csdn.net/chuige2013/article/details/123783612 RocketMQ详细配置与…

竞赛 大数据疫情分析及可视化系统

文章目录 0 前言2 开发简介3 数据集4 实现技术4.1 系统架构4.2 开发环境4.3 疫情地图4.3.1 填充图(Choropleth maps)4.3.2 气泡图 4.4 全国疫情实时追踪4.6 其他页面 5 关键代码最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 大数据疫…

宝塔 php修改了php.ini配置不生效

最近在使用hypref&#xff0c;php的版本是7.4 服务器linux&#xff0c;用宝塔安装完php,并装完swoole插件后 安装了swoole后&#xff0c;需要在php.ini中修改一下配置文件 添加 swoole.use_shortnameOff 但是添加了&#xff0c;重启php,依然不生效 解决方法是&#xff1a; 同时…

数字时代古文的传承———云南文化瑰宝“爨文化“(我为家乡发声)

文章目录 前言⭐ "爨"意味着什么&#xff0c;究竟何为"爨文化"&#xff1f;⭐ 爨文化鲜明的特点1.经济生活2.政治生活3.文化艺术 ⭐ 数字时代古文的传承与传播1.藏品数字化2.建立数据库3.传播大众化 前言 爨文化是继古滇文化之后崛起于珠江正源南盘江流域…

[Linux] 6.VMware虚拟机网络配置

在VMware虚拟机下可以在虚拟网络编辑器看到三种模式 一、Bridged&#xff08;桥接模式&#xff09; 桥接模式就是将主机网卡与虚拟机虚拟的网卡利用虚拟网桥进行通信。 真机、虚拟机都有自己的ip地址&#xff0c;能互相通讯&#xff0c;而且能上网。 功能齐全&#xff0c;但…

【Django笔记】5 Django模板

1. Django-bootstrap3 Bootstrap 是要给CSS/HTML 框架 Django-bootstrap3 是Bootstrap3 集成到Django中&#xff0c;作为Django 的一个应用。 (3 表示版本号) Django-bootstrap3 安装 django-bootstrap3 PyPI Installation Install using pip: pip install django-boots…

雷达编程实战之功耗优化技术(低功耗)

本篇文章以xWRL6432为例&#xff0c;首先介绍了芯片内部的电源管理框架&#xff0c;在产品业务处理流程的不同阶段&#xff0c;我们可以对不同电源域进行相应的开/关来降低功耗。然后介绍了不同的硬件电源参考设计对芯片功耗的影响&#xff0c;又着重介绍了线性调频脉冲相关参数…

1.2.C++项目:仿muduo库实现并发服务器之时间轮的设计

文章目录 一、为什么要设计时间轮&#xff1f;&#xff08;一&#xff09;简单的秒级定时任务实现&#xff1a;&#xff08;二&#xff09;Linux提供给我们的定时器&#xff1a;1.原型2.例子 二、时间轮&#xff08;一&#xff09;思想&#xff08;一&#xff09;代码 一、为什…

2023年显著性检测论文及代码汇总(3)

ACM MM Point-aware Interaction and CNN-induced Refinement Network for RGB-D Salient Object Detection code Abstacrt&#xff1a;近年来&#xff0c;CNN在特征提取和跨模态交互中得到了充分的利用&#xff0c;但在自模态和跨模态的全局远程依赖关系建模方面仍存在不足。…

二叉树的常见操作

二叉树的常见操作 注&#xff1a;二叉树的结构如下&#xff1a; typedef char BinaryTreeDataType; typedef struct BinaryTreeNode {struct BinaryTreeNode* left;struct BinaryTreeNode* right;BinaryTreeDataType data; }BTNode;以下所有操作都以下面的树为例&#xff1a;…

PHP8中final关键字的应用-PHP8知识详解

在PHP8中&#xff0c;final的中文含义是最终的、最后的意思。被final修饰过的类和方法就是“最终的版本”。 如果关键字final放在类的前面&#xff0c;则表示该类不能被继承。 如果关键字final放在方法的前面&#xff0c;则表示该 方法不能被重新定义。 如果有一个类的格式为…