7)线程池进阶及ForkJoinPool线程池

目录

1 )核心线程池的底层实现(ExecutorService类)

1.1)

1.2)threadPoolExecutor的构造方法:

1.3)workQueue工作对列是指提交未执行的任务队列:

1.4)拒绝策略

1.5)扩展RejectedExecutionHandler接口

1.6)扩展ThreadFactory接口

2)监控线程池

3)扩展线程池--监控每个任务的开始和结束

3)优化线程池大小

4)线程池死锁

5)线程中的异常处理

5.1)submit()提交线程,如果抛出异常,会被线程池吃掉。实际上没被吃掉,只是返回到Future了 

5.2)可以将submit()修改为execute

5.3)重写submit()方法,使其抛出异常

6)ForkJoinPool线程池


1 )核心线程池的底层实现(ExecutorService类)

1.1)

查看Executors工具类中newCachedThreadPool(),newSingleThreadExcecutor(),new FixedThreadPool()源码

1.2)threadPoolExecutor的构造方法:

 public ThreadPoolExecutor(int corePoolSize, //指定线程池中核心线程的数量
                              int maximumPoolSize, //指定线程池中最大线程的数量
                              long keepAliveTime,//当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,既空闲线程在多长时长内销毁
                              TimeUnit unit,//是keepAliveTime时长单位
                              BlockingQueue<Runnable> workQueue,//任务队列,把任务提交到该任务任务队列中等待执行
                              ThreadFactory threadFactory,//线程工厂,用于创建线程
                              RejectedExecutionHandler handler) //拒绝策略,当任务太多来不及处理,如何拒绝

1.3)workQueue工作对列是指提交未执行的任务队列:

  1. 直接提交队列,由SynchronousQueue对象提交,实际上就是不缓存,直接交给线程执行,没有多余线程则创建,如果线程超过了最大线程数量,执行拒绝策略
  2. 有界任务队列,由ArrayBlockingQueue对象提交,先判断线程数是否小于核心线程数,在判断任务队列是否已满,最后判断线程数是否大于最大线程数
  3. 无界任务队列,由LinkedBlockingQueue对象实现。只要系统资源未耗尽,一直向队列缓存。当有新任务时,在系统线程数小于corePoolsize核心线程数则创建新的线程,否则进入阻塞对列
  4. 优先任务队列,由PriorityBlockingQueue实现的,是带有任务优先级的队列,是一个特殊的无界队列。在PriorityBlockingQueue队列中可以根据任务优先级顺序先后执行。

1.4)拒绝策略

ThreadPoolExecutor构造方法的最后一个参数指定了拒绝策略,当提交给线程池的任务量超过实际承能力(线程已经用完,等待队列也满了)时,如何处理

  1. AbortPolicy策略,会抛出异常(Executors工具类默认策略)
  2. CallerRunsPolicy策略,只要线程池没关闭,会在调用者线程中运行当前被丢弃的任务
  3. DiscardPolicy直接丢弃这个无法处理的任务
  4. DiscardOldestPolicy 将任务队列中最老的任务丢弃,尝试再次提交新任务

1.5)扩展RejectedExecutionHandler接口

 //定义任务
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int num = new Random().nextInt(5);
                System.out.println(Thread.currentThread().getId() + "..."+System.currentTimeMillis()+"开始睡眠"+num+"秒");

                try {
                    TimeUnit.SECONDS.sleep(num);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //创建线程池,自定义拒绝策略
        //LinkedBlockingDeque是无界队列,但10限止了其大小
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
                //runnable就是请求的任务,threadPoolExecutor就是当前线程池
                System.err.println(r+"is discarding...");
            }
        });

        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            threadPoolExecutor.submit(r);
        }

1.6)扩展ThreadFactory接口

例:使用ThreadFactory创建的接口都属于守护线程

 //定义任务
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int num = new Random().nextInt(10);
                System.out.println(Thread.currentThread().getId() + "..."+System.currentTimeMillis()+"开始睡眠"+num+"秒");

                try {
                    TimeUnit.SECONDS.sleep(num);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new SynchronousQueue(), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                Thread t = new Thread(runnable);
                //设置创建的线程都是守护线程
                t.setDaemon(true);
                return t;
            }
        });
        for (int i = 0; i < 5; i++) {
            executorService.execute(r);
        }

2)监控线程池

对线程池运行状态进行监控

  • int getActiveCount() 获得线程池中当前活动线程的数量(活动指正在执行)
  • long getCompletedTaskCount() 返回线程池完成任务的数量
  • int getCorePoolSize() 线程池中核心线程的数量(等于corePoolSize参数)
  • int getLargestPoolSize() 返回线程池曾经达到的线程的最大数
  • int getMaximumPoolSize() 返回线程池的最大容量(等于maximumPoolSize参数)
  • int getPoolSize() 当前线程池的大小 
  • BlockingQueue<Runnable> getQueue() 返回阻塞队列
  • long getTaskCount() 返回线程池收到的任务总数(有效的任务数,丢弃的任务不算)
//定义任务
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId() + "..."+System.currentTimeMillis()+"开始睡眠"+10+"秒");

                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),new ThreadPoolExecutor.DiscardPolicy());

        for (int i = 0; i < 30; i++) {
            poolExecutor.execute(r);
            System.out.println(" 当前线程池核心线程数量 :" + poolExecutor.getCorePoolSize() + ", 最大线程数:" + poolExecutor.getMaximumPoolSize() + ",当前线程池大小 :" + poolExecutor.getPoolSize() + ",活动线程数量 :" + poolExecutor.getActiveCount()+ ",收到任务数量:" + poolExecutor.getTaskCount() + ",完成任务数 : " + poolExecutor.getCompletedTaskCount() + ", 等待任务数:" + poolExecutor.getQueue().size()) ;
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
===
11...1613299015374开始睡眠10秒
 当前线程池核心线程数量 :5, 最大线程数:10,当前线程池大小 :1,活动线程数量 :1,收到任务数量:1,完成任务数 : 0, 等待任务数:0

解释:

  • 当前线程池核心线程数量: 一直为5  等于corePoolSize参数
  • 最大线程数:一直为10 等于maximumPoolSize参数
  • 当前线程池大小 先是从1到5,然后等待任务数从1到5后,线程池大小再从5到10。与 ArrayBlockingQueue等待对列有关
  • 活动线程数量,先是从1到5,然后等待任务数从1到5后,活动线程再从5-10,与ArrayBlockingQueue等待对列有关
  • 收到任务数量,从0-15,然后开始丢弃任务,然后完成一个才能接收一个 DiscardPolicy设置
  • 等待任务数,先是0,当线程池大小到达核心线程数量后,从0到5

3)扩展线程池--监控每个任务的开始和结束

我们要监控任务只要重写上面两个方法即可。

//定义任务类
    private static class MyTask implements Runnable{
        String name;
        public MyTask(String name){this.name = name;}
        @Override
        public void run() {
            System.out.println(name+"任务正在被线程 "+Thread.currentThread().getId()+"执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //定义扩展线程池,可以定义线程池类继承ThreadPoolExecutor,在子类中重写beforeExecute()/afterExecute()方法
        //也可以直接使用ThreadPoolExecutor的内部类
        ExecutorService executorService = new ThreadPoolExecutor(5,5,0, TimeUnit.SECONDS,new LinkedBlockingQueue<>()){
            @Override
            protected void beforeExecute(Thread thread, Runnable runnable) {
                System.out.println(thread.getId()+"线程准备执行任务:"+((MyTask)runnable).name);
            }

            @Override
            protected void afterExecute(Runnable runnable, Throwable throwable) {
                System.out.println(((MyTask)runnable).name+"任务执行完毕");
            }

            @Override
            protected void terminated() {
                System.out.println("线程池退出");
            }
        };
        for (int i = 0; i < 5; i++) {
            MyTask task = new MyTask("task-"+i);
            executorService.execute(task);
//            executorService.submit(task); 使用submit提交有强转错误
        }
        executorService.shutdown(); //关闭线程池仅仅是说线程池不再接收新的任务,线程也已接收的任务正常执行完毕
    }
====
12线程准备执行任务:task-1
11线程准备执行任务:task-0
task-1任务正在被线程 12执行
task-0任务正在被线程 11执行
task-1任务执行完毕
task-0任务执行完毕
线程池退出

3)优化线程池大小

线程池极大极小都会影响性能,一般来说,线程池大小需要考虑cpu数量,内存大小等因素。

线程池大小 = cpu的数量×目标cpu的使用率×(1+等待时间/计算时间)

4)线程池死锁

与多线程死锁有区别,线程池死锁说的以因为线程池的原因导致了程序的死锁。

任务A在执行的过程中又向线程池提交了任务B,并且任务A依赖任务B的执行结果。但因为线程池负载的原因,任务B未执行,导致死锁。 

5)线程中的异常处理

使用submit()提交线程,出现异常也不会显示

5.1)submit()提交线程,如果抛出异常,会被线程池吃掉。实际上没被吃掉,只是返回到Future了 

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new SynchronousQueue<>());
        Future<?> s = threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                int i = 1 / 0;
            }
        });
         try {
            Thread.sleep(2000);
            System.out.println("提交的异常:"+s.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

5.2)可以将submit()修改为execute

  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new SynchronousQueue<>());
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                int i = 1 / 0;
            }
        });

5.3)重写submit()方法,使其抛出异常

实际上也就是重写了run方法

private static class TraceThreadPoollExecutor extends ThreadPoolExecutor{
        public TraceThreadPoollExecutor(int i, int i1, long l, TimeUnit timeUnit, BlockingQueue<Runnable> blockingQueue) {
            super(i, i1, l, timeUnit, blockingQueue);
        }
        //定义方法,对执行的任务进行包装
        public Runnable wrap(Runnable task, Exception e){
            return new Runnable() {
                @Override
                public void run() {
                    try {
                        task.run();
                    }catch (Exception e){
                        e.printStackTrace();
                        throw e;
                    }
                }
            };
        }

        @Override
        public Future<?> submit(Runnable runnable) {
            return super.submit(wrap(runnable,new Exception("客户跟踪异常")));
        }
    }

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new TraceThreadPoollExecutor(5, 5, 0, TimeUnit.SECONDS, new SynchronousQueue<>());
        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                int i = 1 / 0;
            }
        });
    }

6)ForkJoinPool线程池

ForkJoinPool采用分而治之原理,是大数据处理方法。把一个大任务调用fork()方法分解为若干小的任务,把小任务的处理结果进行join()合并为大任务的结果。

ForkJoinPool线程池最常用的方法是:<T> ForkJoinTask<T> submit(ForkJoinTask<T> task>向线程池提交一个ForkJoinTask任务,ForkJoinTask任务支持fork()分解与join()等待的任务。ForkJoinTask有两个重要的子类,RecursiveAction和RecursiveTask,区别在于RecursiveAction任务没有返回值。

在ForkJoinPool线程池中,线程的创建数是随程序的运行决定的,而不是人为指定的。

fork()就像开启一个线程,去执行compute()。而join()就是去获取 compute()执行的结果(没执行完等待)。

且是通过ForkJoinPool去调用继承RecursiveTask的类

public class Test09 {
    //计算数列的和,需要返回结果,可以定义任务继承RecursiveTask
    private static class CountTask extends RecursiveTask<Long>{
        private static final int THRESHOLD = 10000; //定义数据规模的阈值,允许计算10000个数内的和,超过该阈值的数列就需要分解
        private static final int TASKNUM = 100; //定义每次把大任务分解为100个小任务
        private long start =0;//开始
        private long end = 0;//结束

        public CountTask(long start, long end) {
            this.start = start;
            this.end = end;
        }

        //重写RecursiveTask类的compute()方法,计算数列的结果
        @Override
        protected Long compute() {
            long sum = 0;
            if(end -start< THRESHOLD){
                for (long i = start;i <= end; i++) {
                    sum += i;
                }
            }else {
                //约定每次分解成100个小任务,计算每个任务的计算量
                long step=(start+end)/TASKNUM;
                ArrayList<CountTask> subTaskList=new ArrayList<>();
                long pos = start;
                for (int i = 0; i < TASKNUM; i++) {
                    long lastOne = pos+ step;
                    if(lastOne > end) lastOne = end;
                    //创建子任务
                    CountTask task = new CountTask(pos,lastOne);
                    //把任务添加到集合中
                    subTaskList.add(task);
                    //调用for()提交子任务
                    task.fork();
                    //调整下个任务的起始位置
                    pos += step+1;
                }
                //等待所有的子任务结束后,合并计算结果。
                for (CountTask task : subTaskList) {
                    sum += task.join();
                }
            }
            return sum;
        }
    }

    public static void main(String[] args) {
        //创建ForkJoinPool线程池
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //创建一个大任务
        CountTask task = new CountTask(0L,200000L);
        //把大任务提交给线程池
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);

        try {
            Long res = submit.get();
            System.out.println("计算结果为:"+res);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long r=0;
        for (int i = 0; i <= 200000L; i++) {
            r+=i;
        }
        System.out.println(r);
    }
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,ForkJoinPoolJava中的一个线程池,主要用于执行分治任务。它是Java 7引入的一个新特性,可以利用多核处理器提高并行计算性能。ForkJoinPool使用工作窃取算法,即当一个线程的任务执行完后,它会从其他线程的任务队列中窃取任务来执行,以保证各个线程的任务负载较为均衡。 ForkJoinPool的使用方法与其他线程池类似,可以通过构造函或者静态工厂方法来创建线程池。例如: ``` ForkJoinPool pool = new ForkJoinPool(); ``` 这样就创建了一个默认的ForkJoinPool线程池,它的线程等于CPU核心。也可以通过构造函来指定线程池的参,例如: ``` ForkJoinPool pool = new ForkJoinPool(4); ``` 这样就创建了一个包含4个线程ForkJoinPool线程池。在使用ForkJoinPool时,需要定义一个ForkJoinTask任务,例如: ``` class MyTask extends RecursiveTask<Integer> { protected Integer compute() { // 执行任务 } } // 创建任务 MyTask task = new MyTask(); // 执行任务 int result = pool.invoke(task); ``` 这里的MyTask是一个继承自ForkJoinTask的任务,它的compute()方法中定义了任务的具体执行过程。执行任务的方式是通过ForkJoinPool的invoke()方法来调用,它会返回任务的执行结果。 当然,除了invoke()方法之外,ForkJoinPool还提供了其他一些方法来执行任务,例如submit()和execute()方法。同时,ForkJoinPool也支持设置线程池的一些属性,例如任务窃取的策略、线程池的名称等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值