Java多线程实战|线程池的原理及使用

890 阅读6分钟

这是我参与更文挑战的第16天,活动详情查看: 更文挑战

线程池介绍:

线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

线程池7大参数:

  1. corePoolSize:核心线程数
  2. maximumPoolSize:最大线程数
  3. keepAliveTime:大于空闲时间时多余线程销毁
  4. unit:时间单位
  5. workQueue:任务队列
  6. threadFactory:线程工厂
  7. RejectedExecutionHandler:拒绝策略

如何配置合适的线程池:

我们都知道线程池有着它的核心线程数及最大线程数等等,在我们自定义线程池的时候这些都需要我们自己去做定义,那么如何来定义线程池的线程数呢,我们通常把线程池处理的类型分为如下3种:

  1. CPU密集型任务

尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

  1. IO密集型任务

可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

  1. 混合型任务
  1. 可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
  2. 因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。
  3. 因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

线程池执行方式execute()和submit():

  1. execute(),执行一个 任务,没有返回值。 2、submit(),提交一个线程任务,有返回值。
  1. submit(Callable task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。
  2. submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。
  3. submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。
  4. Future.get方法会使取结果的线程进入阻塞状态,知道线程执行完成之后,唤醒取结果的线程,然后返回结果。

代码示例:

public class WatcherThreadPoolExecutor extends ThreadPoolExecutor {
    private String watcherName;
    public WatcherThreadPoolExecutor(String watcherName, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        this.watcherName = watcherName;
    }
    public <T> Future<T> submit(Callable<T> task) {
        monitor();
        return super.submit(Tracer.wrap(task));
    }
    public <T> Future<T> submit(Runnable task, T result) {
        monitor();
        return super.submit(Tracer.wrap(task), result);
    }
    public Future<?> submit(Runnable task) {
        monitor();
        return super.submit(Tracer.wrap(task));
    }
    private void monitor() {
        log.info("{}-队列剩余:{}", watcherName, this.getQueue().remainingCapacity());
    }
}

线程池状态关键字: volatile int runState;

  • static final int RUNNING = 0;
  • static final int SHUTDOWN = 1;
  • static final int STOP = 2;
  • static final int TERMINATED = 3;
  1. 当创建线程池后,初始时,线程池处于RUNNING状态;
  2. 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
  3. 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
  4. 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

execute实现原理(submit方法里面最终调用的还是execute()方法):

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

原理分析:(实质为一个单例模式)

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

原理分析:
  (实质为一个单例模式)
1.
(command == null)
    判断提交任务是否为空,否则抛空指针
2.
poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)
    判断当前线程池的核心线程数是否>=核心线程数 成立进入if语句,反之执行addIfUnderCorePoolSize(command)方法;
    2.1:如果addIfUnderCorePoolSize(command)返回false则继续执行返回ture则整个方法执行完毕(方法如其名add核心线程)

3.
runState == RUNNING && workQueue.offer(command)
    判断当前线程池是否处于RUNNING状态,是->放入缓存队列,
    如果当前线程池不处于RUNNING状态或者任务放入缓存队列失败,执行addIfUnderMaximumPoolSize(command)方法(方法如其名add最大线程);
4.
reject(command)
如果执行addIfUnderMaximumPoolSize方法失败,则执行reject()方法进行任务拒绝处理
5.
runState != RUNNING || poolSize == 0
  这句判断是为了防止在将此任务添加进任务缓存队列的同时
   其他线程突然调用shutdown或者shutdownNow方法关闭了线程池的一种应急措施。
6.
ensureQueuedTaskHandled(command)
    如果是这样就执行:保证 添加到任务缓存队列中的任务得到处理。

原理如图: 图片.png 任务缓存队列及排队策略:

workQueue的类型为BlockingQueue,通常可以取下面三种类型:

  • ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
  • LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
  • synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。 任务拒绝策略:
  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

线程池的关闭:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

ok!今日分享到此结束,希望可以对大家有帮助,有不对的地方希望大家可以提出来的,共同成长;

整洁成就卓越代码,细节之中只有天地