关于线程池的几个问题

这里以java中的   ThreadPoolExecutor  线程池来说明:

1:线程池的几个重要的参数,这几个参数有什么作用?

    在线程中有 核心池大小,最大池大小,任务队列  这几个比较重要的参数。

    以下是几个重要参数在源码中的体现

//  核心池大小
private volatile int corePoolSize;
//  最大池大小
private volatile int maximumPoolSize;
//  任务队列
private final BlockingQueue<Runnable> workQueue;

   作用:

   corePoolSize:当需要执行的任务数量小于corePoolSize则新创建一个线程,并且执行任务,如果执行任务数大于corePoolSize则将任务,则将需要执行的任务放入到workQueue队列中,以下是代码:

//  小于核心池大小,则新增一个线程然后执行
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);
        }

   maximumPoolSize:当队列中的任务已经添加满了,再往池中添加任务,如果 池中线程数<maximumPoolSize则会新建线程,反之则废弃该任务

// 添加到队列中失败,则会新建线程去执行任务
else if (!addWorker(command, false))
            reject(command);

  workQueue:当池中的线程数大于等于核心池的大小,则将需要执行的任务放入到队列workQueue中。

2:线程池的工作流程是什么?

  分为3中场景的流程:1:池中线程数<核心池数   2:池中线程数大于等于核心池数但小于最大池数 3:池中线程=最大池数

  场景1:新建一个线程并且执行任务,进入到 addWorker 的方法中:

// 添加一个任务到workSet中,core  为true
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            // 获取线程池的运行状态
            int rs = runStateOf(c);
            ……
            for (;;) {
                // 线程池中运行的线程数
                int wc = workerCountOf(c);
                
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //  原子增加 执行线程的数量 
                if (compareAndIncrementWorkerCount(c))
                    // 跳出循环 进入循环体外的逻辑
                    break retry;
                 ……
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 将任务封装成Worker对象
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                //  这里通过加锁,保证线程安全 保证添加任务安全
                mainLock.lock();
                try {
                    ……
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //  将任务放到 workers 集合中 在锁中添加
                        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;
    }

 下面看看具体的执行方法  即   t.start(); 方法的具体逻辑,代码如下:

//执行 start方法  会运行run 方法
public void run() {
            runWorker(this);
        }

下面是  runWorker  的方法:task != null表示需要执行的任务不为空,在池中线程<核心池大小为true     getTask():从队列中获取任务并且执行。

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
       try {
            // 进入while循环,使该线程可以一直处理任务,线程不会执行完一次任务就退出,在线程池退出前,线程一直工作,以免线程资源的浪费
            while (task != null || (task = getTask()) != null) {
                w.lock();
                
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        // 具体执行任务
                        task.run();
                    } 
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

当这个线程执行完任务之后,并不会销毁线程,而是不断遍历队列中的任务,如果队列中有任务,则执行相应的任务。这个是线程池的主要实现逻辑,即达到了线程复用的目的。

 场景2:池中线程数大于等于核心池数但小于最大池数  

//  这里将任务放入到队列中
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);
        }

放入到队列中,具体的执行时刻是在,场景1中的  while循环中的  getTask() 获取到任务,然后执行

场景3:

// 将任务加到workerSet中,并且新建一个线程去执行,这里的core标识为false, 其余逻辑和场景1中是一致的
else if (!addWorker(command, false))
            reject(command);

 

3:如何设置核心池的大小?如何计算?

如果是计算密集型,这时是不需要切换线程来计算的,开启的线程数为  CPU个数

如果是IO阻塞密集,则根据实际的阻塞时间来进行计算,如:执行计算时间  为 a   阻塞时间为  b  这时线程池的大小应该设置为  (b/a+1)*cpu 核数   这个只是理论指导值    

posted @ 2020-11-28 16:15  beppezhang  阅读(152)  评论(0编辑  收藏  举报