线程池

为什么使线程池

java的线程对应是操作系统级别的线程调用,不断的或无限制的启停线程是对系统资源的一种消耗也会增加系统的不稳定性。同时很多个线程间的运行上下文切换也会降低实际系统的性能。在对一些大量耗时较小的任务不断的启停线程会降低性能。这个时候就比较适合使用线程池。线程池在池中维护一定的工作线程不断的接收请求处理,同时将临时处理不完的任务放入等待队列进行等待,等待池中线程获取调度处理。这样不会因为瞬时任务高并发导致线程的过多并发。

配置参数

线程池在初始化的时候提供几个参数用来维护线程池线程数

corePoolSize

核心线程数。线程池创建到销毁,一直存活的线程数量

maximumPoolSize

线程池最大线程数。当一个新task提交到线程池,这个时候如果所有的核心线程数都被使用并且等待队列已满,则这个时候会新建线程处理task,临时创建的线程数加上核心线程数要小于maximumPoolSize

keepAliveTime

临时创建的线程空闲多长时间后会被回收关闭

等待队列

无界队列:LinkedBlockingQuene队列,队列长度无上限。fixSize 线程池和single线程池使用该队列

有界队列:ArrayBlockingQueue队列。队列长度有上限

同步切换队列:队列不存储元素,当且仅当有另一个线程同时在另一端选择相同的任务时,我们可以将任务排队。cache线程池使用该队列

拒绝策略

如果当前线程池中有空闲线程执行新提交的任务,否则任务进入等待队列。如果等待队列也满了呢。这个时候就需要一种机制来处理线程池不能接收的任务。这里默认有四种策略,在创建线程池的时候可以指定拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

线程池的构造方法,RejectedExecutionHandler 可以指定饱和策略。

AbortPolicy:默认策略。直接抛出RejectedExecutionException异常

CallerRunsPolicy:用调用者自己所在的线程来执行当前任务

DiscardPolicy:直接丢弃

DiscardOldestPolicy:丢弃等待队列中最靠前(先入队)的任务,执行当前任务。这里本来等待时间最长的被执行的优先级应该最高,却直接被抛弃了

自定义策略:实现RejectedExecutionHandler接口rejectedExecution方法。

class GrowPolicy implements RejectedExecutionHandler {
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        lock.lock();
        try{
            //调大线程池最大线程数
            executor.setMaximumPoolSize(executor.getMaximumPoolSize()+1);
        }finally {
            lock.unlock();
        }
        //将当前任务提交到线程池
        executor.submit(r);
    }
}

线程池创建使用

可以通过Executors工具类来创建线程池,最后都是创建一个ThreadPoolExecutor实例,只是参数不同。ThreadPoolExecutor继承AbstractExecutorService,AbstractExecutorService实现了ExecutorService接口

  • 单线程线程池

    核心线程数 = 最大线程数 = 1,空闲超时时间是0。线程池中只有一个工作线程。

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(()->{
            System.out.println("run");
        });

        Future<String> result = executor.submit(() -> {
            return "end";
        });
//阻塞等待future结果
        System.out.println(result.get());

可以通过execute()向线程池提交一个任务(实现Runnable接口),也可以通过submit方法向线程池提交一个任务。submit提交一个Callable任务。

  • 固定线程线程池

核心线程数 = 最大线程数 = 指定线程数

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
  • 缓存线程池

核心线程数0,最大线程数Integer.MAX_VALUE,空闲时间60s。等待队列使用的SynchronousQueue。该等待队列的特点是不保存元素。每个insert操作必须有另一个线程执行remove操作。否则insert操作就会阻塞。

ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
  • 计划任务线程池

    周期性的重复执行任务

System.out.println(LocalTime.now()+": schedule pool start");
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
//延迟固定时间后执行
executor.schedule(()->{
    System.out.println(LocalTime.now()+": schedule run");
},2,TimeUnit.SECONDS);

//固定周期运行 延迟3秒后,每隔5秒执行一次
CountDownLatch latch = new CountDownLatch(5);
ScheduledFuture<?> future = executor.scheduleAtFixedRate(() -> {
    System.out.println(LocalTime.now() + ": fix rate run");
    latch.countDown();
}, 3, 5, TimeUnit.SECONDS);

latch.await();
System.out.println("执行5次,取消schedule");
future.cancel(true);

线程池状态

线程池中定义以下属性用来维护和记录线程池状态

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
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;
private static final int TERMINATED =  3 << COUNT_BITS;

// 获取运行状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 获取线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

线程池使用AtomicInteger的来保存线程池状态和线程数。将AtomicInteger分成两部分,高三位表示线程池状态,后29位表示线程数。最大线程数CAPACITY 为((2^29)-1)约5亿个。

RUNNING(111) 运行状态,接收和执行队列任务。

SHUTDOWN(000) 不接受新任务,会继续执行队列中任务

STOP(001) 不接受新任务,不执行队列任务,正在执行中的任务会中断

TIDYING(010) 整理状态,所有的任务都被终止,所有工作线程也为0

TERMINATED(011) 终止,terminated()方法执行完成。

状态实际对应数值也是 RUNNING(负数) < SHUTDOWN(0)< STOP ...

状态流转:

RUNNING -> SHUTDOWN 运行中的线程池执行shutdown()方法会变成SHUTDOWN状态。

(RUNNING or SHUTDOWN) -> STOP 执行shutdownNow() 会变成STOP状态

SHUTDOWN -> TIDYING 所有的队列和线程池为空后变成TIDYING状态

TIDYING->TERMINATED

线程池监控

线程池提供以下方法可以查看当前线程池运行状态

getPoolSize() 当前存活线程总数

getActiveCount() 当前活动线程数(执行task)

executor.getCompletedTaskCount() 已完成任务数

executor.getQueue().size() 等待队列长度

executor.getLargestPoolSize() 峰值线程数

源码阅读

线程池主要通过execute方法用来执行提交到线程池的任务。

execute方法:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    int c = ctl.get();
    //1、当前运行线程数是否少于核心线程数
    if (workerCountOf(c) < corePoolSize) {
        //新增worker,运行线程   
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2、当前线程池状态时running,然后尝试入队
    if (isRunning(c) && workQueue.offer(command)) {
		//入队成功、二次检查线程池状态
        int recheck = ctl.get();
        //如果线程池已经关闭,则从队列中删除该任务,然后执行拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)//判断当前工作线程数是否为0,
            addWorker(null, false);//添加一个新工作线程,但是不立即执行当前任务。第二个参数false表示使用maximumPoolSize来判断线程上限
    }
    //3、入队失败,再次尝试创建线程
    else if (!addWorker(command, false))
        reject(command);//创建失败,执行拒绝策略
}

当线程池接收到新任务执行后,首先判断当前线程池线程数是否少于核心线程数,如果是则会执行addworker方法在线程池中创建一个新工作线程来执行该任务。否则就将当前任务放入等待队列。如果入队失败就执行拒绝策略。

addWorker

//firstTask 
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        /**
		*  
		*线程池是shutdown状态时候,判断是否需要添加work。如果队列为空则不添加
		*/
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
 			/**判断是否超过容量,CAPACITY 是最大容量。core 表示是按核心线程数判断还是按最                    大线程数判断
			*/
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
			//添加线程数成功,跳出外层循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            //添加线程数失败,如果线程池状态rs未改变,内层循环continue CAS修改线程数,否则跳出内循环,continue外循环
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
   //开始添加worker
    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 {
                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);//将新建的worker添加到线程池中
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();//启动work
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

线程池使用HashSet<Worker> workers 来维护线程池中的工作线程,每个线程被封装成一个Worker对象,添加工作线程的过程就是创建一个Worker对象放在set集合中。Worker是ThreadPoolExecutor的一个内部类,继承AQS实现了Runnable接口。我们指定线程的start()方法执行完后就会线程结束,这里worker是怎么维护实现线程运行完task不结束的呢?

看下worker里的线程run方法逻辑。主要会调用

        public void run() {
            runWorker(this);
        }

final void runWorker(Worker w) {
    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 pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {//调用task的run方法执行任务
                    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);
                }
            } finally {
                task = null;
                w.completedTasks++;//更新已完成任务数
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
		//worker运行结束
        processWorkerExit(w, completedAbruptly);
    }
}

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        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);

            // 允许核心线程数回收 || 工作线程数>核心线程数
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			/**
			 判断是否需要减少工作线程数,如果需要减少返回null
			*/
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
				//从队列获取一个任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

调用worker的start启动工作线程,其run方法会调用runWorker()。while死循环会不断的从队列获取任务,获取任务成功后调用task 的run()方法执行任务。获取任务方法getTask()会判断当前线程池的配置,是否进行线程池工作线程的回收,如果需要回收则获取任务返回null,runWorker的while方法获取不到任务后就会退出,然后当前worker线程run方法就正常执行结束,就会回收掉。

判断当前工作线程是否需要回收条件:

//允许核心线程数超时回收 || 当前工作线程数大于核心线程数            
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			//timedOut 为true 表示已经执行过一次取任务超时
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

条件1:当前线程数大于最大线程数 || (允许核心线程超时回收并且当前工作线程数大于核心线程数,且线程已经执行过一次取任务超时)

条件2:当前工作线程数大于1 或等待队列为空。

两个条件要同时成立。

队列获取任务poll()方法是等待一定超时时间,take方法是一直阻塞等待。这里timed为ture时候就是用poll方法获取任务,如果获取不到timedOut就会变成true。然后下一次for循环就会判断需要回收该线程。

总结

有任务提交到线程池时,线程池会根据配置参数(核心线程数,最大线程数等)确定是创建一个新的工作线程来处理该任务还是将当前任务加入等待队列。等待队列如果已满会执行拒绝策略。

工作线程被封装成一个内部类Worker。Worker继承了AQS实现了Runnable接口。继承AQS是为了执行任务时候加锁,加锁目的是为了执行任务线程不被中断。像shutdown方法最终会调用interruptIdleWorkers()方法,在interupt当前worker前会先尝试获取锁。这个时候就需要等待task任务执行完成释放锁后才能中断。工作线程的启动和线程一样通过start方法启动。worker会是一直循环从等待队列获取待处理任务,当超时(超过KeepAliveTime)获取不到任务时。worker线程run方法会自然执行完成,该工作线程会被回收掉。

posted @ 2023-06-26 16:23  朋羽  阅读(10)  评论(0编辑  收藏  举报