你不得不了解的JAVA线程池!

阿里巴巴规范说过,使用线程最好是用线程池,那就是说使用线程池有一定的好处,能够管理线程连接,开启用户使用的线程数,用完回归池中,可以让其他线程使用,减少连接线程的资源消耗。

那么Java中有提供ThreadPoolExecutor线程池的类实现,Java也对其封装了Executors的四种静态使用方法,先来讲一下四种线程池的使用。

1.newFixedThreadPool

fixed的意思就是固定, 见名知意就是创建一个固定容量的线程池,用户传要创建几个线程,那么所有的任务都由这几个线程来工作。代码示例如下:

这个代码模拟了开3个线程处理30个任务,看一下输出结果

public static void FixedThreadPoolDemo() throws InterruptedException {
        //创建一个定长线程池
        //定长线程池的特点:固定线程总数,空闲线程用于执行任务,如果线程都在使用的话,后续任务处于等待状态
        //在线程池中的线程执行任务后在执行后续的任务
        //如果任务处于等待的状态,备选的等待算法默认为(FIFO(先进先出))也可以设置LIFO(后进先出)
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 1; i <= 30; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("操作的线程:"+Thread.currentThread().getName()+"  索引:"+index);
                    //list.add(Thread.currentThread().getName().split("-")[3]);
                }
            });
        }
        Thread.sleep(1000);
        // OptionalInt max = list.stream().mapToInt(str -> Integer.parseInt(str.toString())).max();
        //shutdown()代表关闭线程池(等待所有线程完成)
        //shutdownNow()代表立即终止线程池的运行,不等待线程,不推荐使用
        executorService.shutdown();
 public static void main(String[] args) throws InterruptedException {
        FixedThreadPool.FixedThreadPoolDemo();
    }

可以看出线程使用一直都是1,2,3个线程,不会在额外开辟线程

点进去看newFixedThreadPool方法,内部使用的ThreadPoolExecutor类创建线程池,讲一下参数把。

第1个参数:核心线程数,传的是3,这里nThreads就是那个3,

第2个参数:最大线程数也设置的是nThreads,代表我们所开的线程最大的也就是3个,

第3个参数:keepAliveTime最大线程数空闲存活时间,这里的0L相当于空闲了立即销毁最大线程数,但是我们的核心线程数也是3,所以不存在销毁

第4个参数:基于第3个参数,是它的单位

第5个参数:队列,用于等待线程处理的任务队列存储。这里用的LinkedBlockingQueue,阻塞的无界队列

2.newCachedThreadPool

还是见名知意可缓存线程池,用户不传要开多少个线程,根据任务进行分配线程,线程数量可能会很多,代码示例如下:

模拟200个任务进行操作,看看会开多少个线程

public static void cacheThreadDemo() throws InterruptedException {
        //创建一个可缓存线程池
        //ExecutorService调度器对象,用于管理线程池
        //Executors.newCachedThreadPool()创建一个可缓存线程池
        //可缓存线程池的特点:无限大,如果线程中没有可用的则创建,有空闲线程则利用起来
        ExecutorService executorService = Executors.newCachedThreadPool();
        // List list = new ArrayList();
        int[] strs = new int[400];
        for (int i = 1; i <= 200; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("操作的线程:"+Thread.currentThread().getName() + "  索引:" + index);
                    //list.add(Thread.currentThread().getName().split("-")[3]);
                    strs[index] = Integer.parseInt(Thread.currentThread().getName().split("-")[3]);
                }
            });
        }
        Thread.sleep(1000);
        int max = Arrays.stream(strs).max().getAsInt();
        System.out.println("开辟空间最大的线程池名称是:" + max);
        executorService.shutdown();
    }
public static void main(String[] args) throws InterruptedException {
        CachedThreadPool.cacheThreadDemo();
}

有开辟的线程,有重用的线程,本次示例开了79个。

内部调用ThreadPoolExecutor,核心线程数是0,最大线程无止尽,直到内存用完,最大线程池空闲存活时间60秒,等待的队列是使用SynchronousQueue,SynchronousQueue是一个内部只能包含一个元素的队列,插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素

3.newSingleThreadExecutor

见名知意,是单例,只会创建一个线程处理任务。代码示例如下:

public static void singleThreadDemo() throws InterruptedException {
        //创建一个单线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 1; i <= 6; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":" + index);
                    //list.add(Thread.currentThread().getName().split("-")[3]);
                }
            });
        }
        Thread.sleep(1000);
        executorService.shutdown();
    }
 public static void main(String[] args) throws InterruptedException {
        singlePool.singleThreadDemo();
    }

结果如下所示:只会创造一个线程来处理任务。

实现是核心线程数设置为1,最大线程池数设置为1,线程池存活时间是空闲立即销毁,队列是阻塞无界队列

4.newScheduledThreadPool

名字里有任务调度,一般java是定期执行一些事情,那么这里也是,延迟定期执行事务,示例代码如下:

public static void scheduledPoolDemo() {
        ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
        // 延迟1秒执行,每3秒执行一次
        scheduledPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("操作线程:" + Thread.currentThread().getName() + "   " + new Date() + "延迟1秒执行,每3秒执行一次");
            }
        }, 1, 3, TimeUnit.SECONDS);
    }
public static void main(String[] args) {
    scheduledPoolDemo();
}

执行结果如下:

咱们的核心线程数是5个,这里最大线程数是无数个,0秒最大线程空闲,队列采用延迟队列方式。

ThreadPoolExecutor讲解

那么这4大线程池介绍完毕了,在介绍下ThreadPoolExecutor,阿里巴巴规范说创建线程池最好用ThreadPoolExecutor,而不要用上述说的4个,原因是自己实现的可以设置队列为不是无界队列,也能控制核心线程数和最大核心线程数,根据自己的业务场景选择合适的参数。下面来讲讲7大参数,

1.corePoolSize:核心线程数,当有任务进来时还未达到核心线程数,则创建核心线程

    1.1 当线程数没有达到核心线程数最大值的时候,新任务会继续创建线程,不会复用线程池的线程

    1.2 核心线程一般不会销毁,空闲也不会销毁,除非通过方法allowCoreThreadTimeOut(boolean value)设置为true时,超时也            同样会被销毁

    1.3 生产环境首次初始化的时候,可以调用prestartCoreThread(),prestartAllCoreThread()来预先创建所有的和核心线程,避免            第一次调用缓慢。

2.maximumPoolSize:最大线程数,线程池中能够容纳(包含核心线程数)同时执行的最大线程数,此知大于等于1

3.keepAliveTime:最大线程池中线程数量超过核心线程数时,多余的空闲线程存活的时间,当空闲时间达到keepAliveTime时,多余线程就会被(最大线程池的线程)销毁,当keepAliveTime设置为0时,表明最大线程池空闲立即销毁。

4.unit:keepAliveTime的单位

5.workQueue:等待执行的任务队列,核心线程满了,新的任务就会在加入等待队列中。

6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认即可。

7.handler:拒绝策略,没有空闲的线程处理任务,并且等待队列已满,再有新任务进来如何来拒绝请求的runnable的策略。

 拒绝策略还蛮重要的,记住这四个不同的拒绝策略:

  7.1 AbortPolicy:直接抛出RejectedExecutionException异常阻止系统正常运行,默认使用的异常。

  7.2 DiscardPolicy:默默丢弃无法处理的任务,不予任何处理也不抛出异常。

  7.3 DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

  7.4 CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,二是将某些任务回退到调用者,从而降低新任务流量。

自定义的线程池代码示例demo

public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3), //不写的话默认也是Integer.MAX_VALUE
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());//默认的拒绝策略

        try {
            for (int i = 0; i <8; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + "办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); //关闭线程池
        }
    }

结果:

线程池执行流程

1.提交任务时,检查核心线程池是否已满,没有满则创建线程,核心线程池满了走2

2.检查队列是否已满,没有则把任务放入队列中,队列满了走3

3.检查最大线程池数是否已满,没有则继续创建线程执行任务,满了走4

4.执行拒绝策略

以上就是线程池的全部内容,希望可以帮到你!下节抽空会讲下队列哦!

猜你喜欢

转载自blog.csdn.net/dfBeautifulLive/article/details/106758163