20201030ThreadPoolExecutor线程池

1,模拟高并发场景

    public static void main(String[] args) {
        //模拟高并发场景
        ExecutorService service = Executors.newFixedThreadPool(100);
        //模拟多少的并发
        for (int i = 0; i < 2000; i++) {
            service.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    //在这里进行高并发的操作,100个线程,某个任务执行2000次。
                    return null;
                }
            });
        }
        service.shutdown();
    }

四种常用的线程池,它们都会得到一个ExecutorService的实现类,ExecutorService才是真正的线程池,现在来说一说ExecutorService的实现类ThreadPoolExecutor。

ExecutorService接口才是真正的线程池,ThreadPoolExecutor是它的实现类。ThreadPoolExecutor是 ExecutorSerivce接口的具体实现。

 

2,ThreadPoolExecutor和四种线程池之间的关系

ExecutorService接口,ThreadPoolExecutor类,6个核心参数,线程池的4种拒绝策略

四种线程池底层根本还是ThreadPoolExecutor对象

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

 

3,使用Executors.newFixedThreadPool(10)的缺点

ExecutorService service = Executors.newFixedThreadPool(10);

不是不能使用newFixedThreadPool,newFixedThreadPool只适用于执行很快的代码,比如调用一个方法传参然后就结束。0.01秒能够执行完,一次来20000个并发量也不怕,只要后续再没有请求.

newFixedThreadPool的阻塞队列大小是没有大小限制的,如果队列堆积数据太多会造成资源消耗。

如果代码执行很慢,前端任务不断涌入,就会造成任务队列阻塞。

固定尺寸的线程池,可变长度的线程池。

在使用线程池之前,如何去创建一个线程池? 如何关闭线程池(pool.shutdown())?  

newFixedThreadPool的参数指定了可以运行的线程的最大数目;超过这个数目的线程加进去以后,不会运行。其次,加入线程池的线程属于托管状态,线程的运行不受加入顺序的影响。 

 

4,线程池ThreadPoolExecutor的创建

ThreadPoolExecutor 是 ExecutorSerivce接口的具体实现

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 6, 2, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(1),
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 8; i++) {
            MyThread myThread = new MyThread();
            threadPoolExecutor.execute(myThread);
        }

6个参数分别为(也可以有第七个参数,线程工厂):

(1) corePoolSize: 核心池的大小, 或者说线程池维护线程的最少数量 (core : 核心)

(2) maximumPoolSize: 线程池中线程的最大数量

(3) keepAliveTime: 线程池维护线程所允许的空闲时间

(4) unit: 线程池维护线程所允许的空闲时间的单位

(5) workQueue: 线程池所使用的缓冲队列

(6) handler: 线程池对拒绝任务的处理策略

handler有四个选择:

1,ThreadPoolExecutor.AbortPolicy(): 抛出java.util.concurrent.RejectedExecutionException异常

2,ThreadPoolExecutor.CallerRunsPolicy(): 重试添加当前的任务,他会自动重复调用execute()方法

3,ThreadPoolExecutor.DiscardOldestPolicy(): 抛弃旧的任务

4,ThreadPoolExecutor.DiscardPolicy(): 抛弃当前的任务

当一个任务通过execute(Runnable)方法欲添加到线程池时:(核心线程数,消息队列,最大线程数,拒绝策略)

1,如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

2,如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

3,如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

4,如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

所以线程池处理任务的优先级是:

核心线程池 > 等待序列 > 非核心线程池 > 拒绝或其他处理措施

想象成工厂的话,核心线程池就是勤奋的工人,有工作来了自主上去工作,然后工作越来越多,工人忙不过来,就放在一边等着(workQueue),然后工作继续变多,老板不得不临时找一些临时工(maximumPoolSize-corePoolSize)来帮忙,然后工作还在变多,老板也没钱请工人了,就回绝(handle)了甲方的任务

线程池的主要处理流程

 

如何使用ThreadPoolExecutor?

继承Thread类, 写一个自己的线程对象

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行中。。。");
        // 等待一段时间,便于观察
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        super.run();
    }
}

然后创建ThreadPoolExecutor类

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 6, 2, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(1),
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 8; i++) {
            MyThread myThread = new MyThread();
            threadPoolExecutor.execute(myThread);
        }
    }

解析:核心池数量是3 , 最大线程大小是6 , 每个线程维护时间是 2s, 队列大小是 1。

循环8次 :
首先3次 , 核心池没满 , 创建3个线程
第4次, 核心池满了, 队列没满, 任务放在队列中等待 (执行即可看到哪个任务会进入到缓存队列中等待)
第5, 6,7 次, 核心池满了, 队列也满了 , 继续创建线程3个 , 达到最大线程数量.
第8次, 因为设置了sleep , 前面的6个线程都没有执行完毕 , 执行策略, 我这里设置的是ThreadPoolExecutor.AbortPolicy , 直接抛出异常
线程sleep时间到了, 执行任务完毕, 队列中的任务获得线程执行。

 

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:

1)newFixedThreadPool和newSingleThreadExecutor:

主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

2)newCachedThreadPool和newScheduledThreadPool:

主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

 

代码推荐使用 :

ThreadFactory类作用:最重要作用就是设置线程名称,多线程调试起来本来就费劲,如果没有标识符,那就更难了。

    public static void createThreadPool() {
        //1,线程创建工厂,可以自定义线程命名格式(为核心线程执行名称)
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setNameFormat("demo-pool-%d").get();

        //2,ThreadPoolExecutor.AbortPolicy()如果任务数量超出线程池处理能力,抛出java.util.concurrent.RejectedExecutionException异常
        ExecutorService pool = new ThreadPoolExecutor(3, 6,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());


        //3,执行线程
        for (int i = 0; i < 8; i++) {
            MyThread thread = new MyThread();
            //线程池来执行这些线程
            pool.execute(thread);
        }
        pool.shutdown();
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值