栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java线程池三大方法,七大参数,四种拒绝策略

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java线程池三大方法,七大参数,四种拒绝策略

线程池的优势

线程池做的工作主要是:控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

线程池的主要特点:线程复用,控制最大并发数,管理线程。

第一:降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。

1、三大方法

使用Executors 工具类来创建线程池的三大方法

方法一:ExecutorService threadPool = Executors.newSingleThreadExecutor();只有单个线程方法二:ExecutorService threadPool = Executors.newFixedThreadPool(5); 创建一个固定的线程池大小方法三:ExecutorService threadPool = Executors.newCachedThreadPool(); 执行很多短期异步任务,线程池根据需要创建新线程,但在先构建的线程可用时将重用他们。可扩容,遇强则强

代码示例

import java.util.concurrent.*;

// Executors 工具类 三大方法
public class Demo01 {

    public static void main(String[] args) {
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();  // 方法一:单个线程
        // ExecutorService threadPool = Executors.newFixedThreadPool(5); //  方法二:创建一个固定的线程池大小
        ExecutorService threadPool = Executors.newCachedThreadPool();   // 方法三:可伸缩的,遇强则强,遇弱则弱

        try {
            for (int i = 0; i < 10; i++) {
                // 使用了线程池后,使用线程池来创建线程

                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() + "ok");
                    });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

结果:
方法一:
只有一个线程

方法二:
只能调到五个线程

方法三:
十个线程都有机会调到

但是在Java并发开发中,有这样的约束,阿里巴巴java开发文档中也提出了

2、七大参数

讲七大参数之前,我们分析一下三大方法的源码:
方法一:

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

方法二:

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

方法三:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  // 最大为21亿,所以可能导致OOM
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }

可以清楚的看到三大方法的核心都是ThreadPoolExecutor()
所以我们看一下ThreadPoolExecutor()的源码

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

我们可以看到七大参数了,其中

int corePoolSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。int maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于1。long keepAliveTime:空闲的线程保留的时间。TimeUnit unit:空闲线程的保留时间单位

TimeUnit.DAYS; //天 
TimeUnit.HOURS; //小时 
TimeUnit.MINUTES; //分钟 
TimeUnit.SECONDS; //秒 
TimeUnit.MILLISECONDS; //毫秒 
TimeUnit.MICROSECONDS; //微妙 
TimeUnit.NANOSECONDS; //纳秒

BlockingQueue workQueue:阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、linkedBlockingQueue、SynchronousQueue可选。ThreadFactory threadFactory:线程工厂:创建线程的,一般不用动RejectedExecutionHandler handler:队列已满,而且任务量大于最大线程的异常处理策略。有以下取值

这就是四大拒绝策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) 
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
3、四大拒绝策略

ThreadPoolExecutor 底层工作原理


举例:8个人进银行办理业务
1、1~2人被受理(核心大小core)
2、3~5人进入队列(Queue)
3、6~8人到最大线程池(扩容大小max)
4、再有人进来就要被拒绝策略接受了(也就是线程数>max+Queue时,触发拒绝策略)

过程:

    在创建了线程池后,开始等待请求。

    当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
    1). 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务:
    2). 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列:
    3). 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非
    核心线程立刻运行这个任务;
    4). 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

    当一个线程完成任务时,它会从队列中取下一个任务来执行。

    当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
    1).如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉
    2).所有线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

代码示例:

线程池的拒绝策略:

RejectedExecutionHandler rejected = null; 
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务,抛出异常 
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务,不抛出异常【如 果允许任务丢失这是最好的】 
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务 删,之后再尝试加入队列 
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么 主线程会自己去执行该任务,回退

代码测试:

public class Demo02 {
    public static void main(String[] args) {
        //获取CPU的核数,我电脑为8
        System.out.println(Runtime.getRuntime().availableProcessors());
        
        //自定义线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                new linkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());// 队列满了,尝试和第一个竞争,也不会抛出异常
        try {
            // 模拟有9,10,11,12,13个顾客过来银行办理业务,观察结果情况 
            // 最大容量为:maximumPoolSize + workQueue = 最大容量数,一旦超过,就调用拒绝策略
            for (int i = 1; i <= 20 ; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        } catch (Exception e) {
            //线程池用完,程序结束,关闭线程池
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

思考题:线程是否越多越好

一个计算为主的程序(专业一点称为CPU密集型程序)。多线程跑的时候,可以充分利用起所有的cpu
核心,比如说4个核心的cpu,开4个线程的时候,可以同时跑4个线程的运算任务,此时是最大效率。

但是如果线程远远超出cpu核心数量 反而会使得任务效率下降,因为频繁的切换线程也是要消耗时间
的。

因此对于cpu密集型的任务来说,线程数等于cpu数是最好的了。

如果是一个磁盘或网络为主的程序(IO密集型)。一个线程处在IO等待的时候,另一个线程还可以在
CPU里面跑,有时候CPU闲着没事干,所有的线程都在等着IO,这时候他们就是同时的了,而单线程的
话此时还是在一个一个等待的。我们都知道IO的速度比起CPU来是慢到令人发指的。所以开多线程,比
方说多线程网络传输,多线程往不同的目录写文件,等等。

此时 线程数等于IO任务数是最佳的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/777963.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号