闭关修炼(五)线程池

传统艺能,点到为止,如果我都明白的话,我也不在这里了。



线程池

在平时的项目中经常创建、启动和销毁一个线程都是非常消耗时的,这是因为多线程的运行状态有新建,就绪,运行,休眠,死亡, 新建出一个线程并不是马上执行,而是转变为就绪状态等待cpu调度,运行完成到销毁也是一段时间的。于是就有人提出了线程池来复用线程,也就是新建、就绪、运行、运行完后不会立刻销毁掉,而是给下一个线程进行复用,不用再次经历新建和就绪的状态。

什么是线程池?

是运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。

线程池的作用?

  1. 重复利用,降低资源的消耗
  2. 提高响应速度,因为线程不再经历等待cpu调度的时间
  3. 提高线程的管理性,线程池进行资源的创建和分配 ,其中有队列的概念。

线程池有哪些创建方式?

线程池的核心是ThreadPoolExecutor类,但是大多情况下使用Executors类中提供好的四种线程池创建函数,Executors封装了ThreadPoolExecutor,这四种线程池的创建函数一定要记住,有下面这四种:

  1. Executors.newCachedThreadPool();
    可缓存线程池,如果线程长度超过超过处理需要,可以灵活回收空闲线程,若无可回收线程,则创建新线程
  2. Executors.newFixedThreadPool();
    定长线程池,控制最大并发数,超出的线程在队列中等待。这个线程池实际应用场景比较多
  3. Executors.newScheduledThreadPool();
    定长线程池,支持定义及周期性任务执行
  4. Executors.newSingleThreadExecutor();
    单线程化的线程池,只会用唯一的工作线程来执行任务,指定所有任务的执行顺序,如FIFO,LILO,优先级。实际场景用的不多

线程池如何配置合理?

遵循两个原则CPU密集和IO密集

什么是cpu密集和IO密集?
CPU密集:用任务管理器可以查看CPU的有核心数,在线程调度频繁的情况下让线程数和CPU数相同

IO密集:操作数据库,IO需要等待、阻塞,CPU需要等待很长的时间,配置成2*CPU核心数。

创建四种线程池

newCachedThreadPool

使用并发包中的Executor类的静态函数newCachedThreadPool,其实它还是用的ThreadPoolExecutor类,也就是说我们也可以用ThreadPoolExecutor自定义一个线程池。

	public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

用法还是so easy的


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test1 {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            int temp = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() +  ": " + temp);
                }
            });
        }
    }
    
}

使用线程池可以复用以前的线程,而不去创建新的线程

newFixedThreadPool

newFixedThreadPool用例

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            int temp = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() +  ": " + temp);

                }
            });
        }
    }

newScheduledThreadPool

newScheduledThreadPool用例

	 public static void main(String[] args) {
	
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            int temp = i;
            // 用schedule,设定定时任务,3秒后启动
            executorService.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() +  ": " + temp);

                }
            },3, TimeUnit.SECONDS);
        }

    }

newSingleThreadExecutor

newSingleThreadExecutor用例

	public static void main(String[] args) {

        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            int temp = i;
            // 唯一线程执行任务
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() +  ": " + temp);

                }
            });
        }
    }

疑问点

疑问点①,为什么这里的temp不用final也能在run方法里面用呢?
是因为jdk1.7/8之后的优化,temp定后,不做修改,声明的变量定义只出现一次,都默认作为final类型

疑问点②,为什么主线程不能停止?如果要停止应该怎么修改代码?

线程池没有shutdown,使用shutdown()方法,shutdown阻塞等待所有正在调度的方法执行完毕后在主线程结束掉线程池

修改后的代码:

	public static void main(String[] args) {

        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            int temp = i;
            // 唯一线程执行任务
            executorService.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    Thread.sleep(200);
                    System.out.println(Thread.currentThread().getName() +  ": " + temp);

                }
            });
        }
        executorService.shutdown();
    }

线程池原理分析

说说线程池的原理?

核心是使用ThreadPoolExecutor类,下面是单线程池创建

	public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

所以主要是弄清ThreadPoolExecutor构造函数

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

int corePoolSize, 核心池大小,
int maximumPoolSize, 最大池大小,正在线程池大小
long keepAliveTime, 存活时间,终止时间,超时秒数
TimeUnit unit,时间单位
BlockingQueue<Runnable> workQueue

线程池执行过程?

线程池中有两个池子,一个核心线程池,另一个是最大线程池,用户提交的线程给线程池,不会立马到最大线程池中去,而是核心线程池中,核心线程池可以理解为初始化的池子,其大小被初始化,缓存线程池CachedThreadPool核心池大小为0,最大线程池表示最大容量是多少,newSingleThreadExecutor中,其corePoolSize和maximumPoolSize都是1,那么就是只有单线程执行任务。

keepAliveTime 和unit
keepAliveTime当线程数大于核心数时,这是多余的空闲线程在终止前等待新任务的最长时间
unit为keepAliveTime的时间单位,如毫秒,秒等

 	* @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument

假定核心线程池大小为5,如果池子里线程超出了5个之后,会将用户提交的任务放到线程缓存队列中,如缓存队列满了到最大线程池中去执行,如果核心线程池有空闲,缓存队列中的任务则到核心线程池去运行,如果最大线程池也满了,会进行拒绝任务策略。
在这里插入图片描述

第五个参数阻塞队列BlockingQueue<Runnable> workQueue,单线程池中用的是new LinkedBlockingQueue(),为什么要用阻塞队列呢?
阻塞队列可以设定超时时间

execute方法源码

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        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);
    }

源码的注释都已经写的很清楚了。

分3个步骤进行。

  1. 如果运行的线程少于corePoolSize线程,尝试用给定的命令启动一个新线程作为它的第一个任务。 addWorker方法的调用会原子化地检查runState和workerCount,因此通过返回false来防止在不该添加线程的时候添加线程的错误报警。
		if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
  1. 如果一个任务可以成功排队,那么我们仍然需要仔细检查是否应该添加一个线程(因为现有的线程可能在上次检查后死亡,或者池子在进入这个方法后关闭了。)所以我们重新检查状态,如果停止了,必要时回滚enqueuing,如果没有,则启动一个新的线程。
		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. 如果不能排队任务,那么我们尝试添加一个新线程。 如果失败了,我们知道我们已经关闭或饱和了,所以拒绝任务。
 	else if (!addWorker(command, false))
            reject(command);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值