并发基础3(线程池)

目录

1:为什么时候用线程池?

2:线程池原理底层介绍(ThreadPoolExecutor)

2.1:正确创建线程池和参数分析

2.2:错误创建线程池

2.3:线程池原理分析(网上找的图,详见java并发编程艺术)

2.4:底层源码验证

3:线程池实战(ThreadPoolExecutor)

3.1:代码示例

3.2:输出结果分析(验证了前边所说的线程池处理流程)

4:线程池返回值代码


1:为什么时候用线程池?

使用线程池的优点:

第一:降低资源消耗,通过重复利用已经创建的线程减少线程的创建和销毁造成的消耗

第二:提高效应速度,当任务到达的时候,不用等待线程的创建就能执行

第三:提高线程的客观理性,线程是稀缺资源,不能无限创建,会对系统造成消耗,使用线程池方便对线程进行统一的管理。

2:线程池原理底层介绍(ThreadPoolExecutor)

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

2.1:正确创建线程池和参数分析

 ExecutorService executorService;
        executorService = new ThreadPoolExecutor(
                5,//核心线程
                10,//最大线程
                3,//(最大线程-核心线程)的存活时间
                TimeUnit.SECONDS,//单位

                //new LinkedBlockingDeque<>(),// 链表的阻塞对列 长度为3 默认长度超级大
                new ArrayBlockingQueue<>(10),//数组的有界队列
                //new SynchronousQueue<>(),//同步队列 只能存一个 必须取出来 才能再次存

                Executors.defaultThreadFactory(),//默认工厂

                //new ThreadPoolExecutor.AbortPolicy()//默认策略 队列+最大值 满了 抛出异常
                //new ThreadPoolExecutor.CallerRunsPolicy()//哪来的去哪里 main线程处理 不会异常
                new ThreadPoolExecutor.DiscardPolicy()//队列满了丢掉任务

        );

线程池参数解析:

1:int corePoolSize  初始化线程池5个

2:int maximumPoolSize 最大线程池10

3:long keepAliveTime 当前程存活时间 无事可做>1秒 并且线程池数量大于初始化线程5个的时候会被回收到corePoolSize大小

4:TimeUnit unit  时间单位

5:BlockingQueue<Runnable> workQueue 阻塞队列 当请求>核心线程(注意不是最大线程) 进入阻塞队列

线程池推荐三种阻塞队列:

        LinkedBlockingQueue:基于链表结构的无界阻塞队列,它可以指定容量也可以不指定容量(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)

        ArrayBlockingQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

        SynchronousQueue :一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。

6:Executors.defaultThreadFactory()  默认线程工厂

        指定创建线程的工厂,可以不指定,默认是Executors.defaultThreadFactory(),也就是给线程设置一些name信息pool-1-thread-8(代表线程号)

7:defaultHandler  默认处理器,线程池队列放不下了,咋办

类名作用
ThreadPoolExecutor.AbortPolicy()直接抛异常
ThreadPoolExecutor.CallerRunsPolicy()由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardPolicy()抛弃当前的任务

还是默认的AbortPolicy()合理,队列放不下,直接异常

2.2:错误创建线程池

既然有正确的创建线程池,当然也有不好的创建方法,Executors类的底层实现便是ThreadPoolExecutor! Executors 工厂方法有:

Executors.newCachedThreadPool()线程池大小=Integer.MAX_VALUE,无界线程池,可以进行自动线程回收


Executors.newFixedThreadPool(int):固定大小线程池


Executors.newSingleThreadExecutor():线程池大小=1


它们均为大多数使用场景预定义了设置。不过在阿里java文档中说明,尽量不要用该类创建线程池。

//创建newCachedThreadPool
 ExecutorService executorService= Executors.newCachedThreadPool();

//源码分析 调用了ThreadPoolExecutor 最大线程数是Integer.MAX_VALUE 显然不合适,
//线程总数太JB大了,会导致内存溢出 
//其他的几种线程池也有问题
  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

2.3:线程池原理分析(网上找的图,详见java并发编程艺术)

这个图就是ThreadPoolExecutor的线程池主要的处理流程,我们第三部的代码会验证这个图的具体流程

线程池的大体工作思路  
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。 
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务 
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理 
5.当线程池中超过corePoolSize数的线程,空闲时间达到keepAliveTime时,关闭空闲线程            6.当设置allowCoreThreadTimeOut(true)时,线程池中核心线程空闲时间达到keepAliveTime也将关闭

2.4:底层源码验证

         int c = ctl.get();
        //工作线程小于核心线程数,则调用addWork创建新的线程执行任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //工作线程大于核心线程,并且线程池是run的时候将线程放入对列
        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);
        }
        //线程添加到addWork失败则抛出异常
        else if (!addWorker(command, false))
            reject(command);

3:线程池实战(ThreadPoolExecutor)

3.1:代码示例

我们建立一个线程池核心线程是5、最大线程是10、对列长度是12,然后创建20个任务来使用这些线程池。代码如下

public class MyThreadPool {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = null;
        try {
            threadPoolExecutor = new ThreadPoolExecutor(
                    5,//初始化线程池5个
                    10,//最大线程池10
                    1,//当线程存活时间  无事可做>1秒 并且线程池数量大于初始化线程5个的时候会被回收
                    TimeUnit.SECONDS,//时间单位

                    //1:ArrayBlockingQuene基于数组的有界对列,先进先出
                    //2:LinkedBlockingQueue基于链表的阻塞无界对列,先进先出,未指定大小为Integer.MAX_VALUE
                    //3:SynchronizeQueue,一个不存储元素的阻塞对列,每一个插入操作要等另外一个线程的移除操作
                    //4:PriorityBlockingQueue一个具有优先级的无限阻塞对列
                    new ArrayBlockingQueue<>(12)//阻塞队列 当请求>核心线程(注意不是最大线程) 进入阻塞队列
            );



            for (int i = 0; i < 30; i++) {
                //此处线程睡眠3秒
                threadPoolExecutor.execute(new MyThread());
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            //shutdown 将线程池转为shutdown 不会提交新的线程,等待线程执行完毕
            threadPoolExecutor.shutdown();

            //shutdown 将线程转为stop状态 不允许提交新的任务,也不会处理阻塞队列中未执行的任务,
            //threadPoolExecutor.shutdownNow();//正在执行的线程立马中断sleep interrupted
        }
    }
}

Myrunable代码如下:

public class MyThread implements  Runnable{
    @Override
    public void run() {
        try {
            // 创建一个Date对象表示当前时间
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String currentTime = sdf.format(date);
            System.out.println("线程名字开始执行:"+Thread.currentThread().getName()+"当前时间:"+currentTime);
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.2:输出结果分析(验证了前边所说的线程池处理流程)

4:线程池返回值代码

参考博文

ThreadPoolExecutor详解_赶路人儿的博客-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值