【多线程】线程池

一,概述

可以以new Thread(()->{线程执行任务}).start();这种形式开启一个线程。当run()方法运行结束,线程对象会被GC释放。
在真实的生产环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时,反而会耗尽CPU资源。如果不对线程进行控制与管理,反而会影响程序的性能。线程开销主要包括:
创建与启动线程的开销;线程销毁的开销;线程调度的开销;线程数量受限CPU处理器数量。
线程池就是有效使用线程的一种常用方式。线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断地从队列中取出任务并执行。
在这里插入图片描述

二,JDK对线程池的支持

JDK提供了一套Executor框架,可以帮助开发人员有效地使用线程池。
在这里插入图片描述

package com.drhj.thread_pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Author: DRHJ
 * Date: 2022/8/23 20:49
 */
public class Test01 {
    public static void main(String[] args) {
        //创建有5个线程大小的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        //向线程池中提交18个任务,这18个任务存储到线程池的阻塞队列中,线程池中这5个线程就从阻塞队列中取任务执行
        for (int i = 0; i < 18; i++) {
            fixedThreadPool.execute(()->{
                System.out.println(Thread.currentThread().getId() + " 编号的任务在执行任务,开始时间: " + System.currentTimeMillis());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

在这里插入图片描述
可见5个线程反复使用

按照要求执行任务

package com.drhj.thread_pool;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 线程池的计划任务
 * Author: DRHJ
 * Date: 2022/8/24 21:20
 */
public class Test02 {
    public static void main(String[] args) {
        //创建一个有调度功能的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        //在延迟2s后执行任务(Runnable任务, 延迟时长, 时间单位)
        scheduledExecutorService.schedule(()->{
            System.out.println(Thread.currentThread().getId() + " -- " + System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);
        //以固定的频率执行任务,开启任务的时间是固定的,在3s后执行任务,以后每隔5s重新执行一次
        scheduledExecutorService.scheduleAtFixedRate(()->{
            System.out.println(Thread.currentThread().getId() + " --在固定频率开启任务-- " + System.currentTimeMillis());
            try {
                TimeUnit.SECONDS.sleep(3);  //睡眠模拟任务执行时间,如果任务执行时长超过了时间间隔,则任务完成后立即开启下一个任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 3, 2, TimeUnit.SECONDS);

        //在上次任务结束后,在固定延迟再次执行该任务,不管执行任务耗时多长,总是在任务结束后的2s再次执行
        scheduledExecutorService.scheduleWithFixedDelay(()->{
            System.out.println(Thread.currentThread().getId() + " --在固定频率开启任务-- " + System.currentTimeMillis());
            try {
                TimeUnit.SECONDS.sleep(3);  //睡眠模拟任务执行时间,如果任务执行时长超过了时间间隔,则任务完成后立即开启下一个任务
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 3, 2, TimeUnit.SECONDS);
    }
}

三,核心线程池的底层实现

查看Executors工具类中newCacheThreadPool(),newSingleThreadExcecutor(),newFixedThreadPool()源码:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

该线程池在极端情况下,每次提交新的任务都会创建新的线程执行,适合用来执行大量耗时短并且提交频繁的任务

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

Excutors工具类中返回线程池的方法都使用ThreadPoolExecutor线程池,这些方法都是ThreadPoolExecutor线程池的封装。
ThreadPoolExecutor的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

各个参数含义:
corePoolSize,指定线程池中核心线程的数量
maximumPoolSize,指定线程池中最大线程数量
keepAliveTime,当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,即空闲线程在多长时长内销毁
unit,是KeepAliveTime时长单位
workQueue,任务队列,把任务提交到该任务队列中等待执行
threadFactory,线程工厂,用于创建线程
handler拒绝策略,当任务太多来不及处理时,如何拒绝
说明:
workQueue工作队列是指提交未执行任务队列,它是BlockingQueue接口的对象,仅用于存储Runnable任务。根据队列功能分类,在ThreadPoolExecutor构造方法中可以使用一下几种阻塞队列:
1)直接提交队列,有SynchronousQueue对象提供,该队列没有容量,提交给线程池的任务不会被真实保存,总是将新的任务提交给线程执行,如果没有空闲线程,则尝试创建新的线程,如果线程数量已经达到maxinumPoolSize规定的最大值则执行拒绝策略。
2)有界任务队列,由ArrayBlockQueue实现,在创建ArrayBlockQueue对象时,可以指定一个容量。当有任务需要执行时,如果线程池数小于corePoolSize核心线程数则创建新的线程;如果大于corePoolSize核心线程数则加入等待队列。如果队列已满则无法加入,在线程数小于maxinumPoolSize指定的最大线程数前提下会创建新的线程来执行,如果线程数大于maxinumPoolSize最大线程数则执行拒绝策略。
3)无界任务队列,由LinkedBlockingQueue对象实现,与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况。当有新的任务时,在系统线程数小于corePoolSize核心线程数则创建新的线程来执行任务;当线程池中线程数量大于corePoolSize核心线程数则把任务加入阻塞队列。
4)优先任务队列,是通过PriorityBlockingQueue实现的,是带有任务优先级的队列,是一个特殊的无界队列。不管是ArrayBlockingQueue队列还是LinkedBlockingQueue队列都是按照先进先出算法处理任务的。在PriorityBlockingQueue队列中可以根据任务优先级顺序先后执行。
在这里插入图片描述

四,拒绝策略

ThreadPoolExecutor构造方法的最后一个参数指定了拒绝策略。当提交给线程池的任务量超过实际承载能力时,如何处理?即线程池中的线程已经用完了,等待队列也满了,无法为新提交的任务服务,可以通过拒绝策略来处理这个问题。JDK提供了四种拒绝策略:
AbortPolicy策略:抛出异常
CallerRunsPolicy策略:只要线程池没有关闭,会在调用者线程中运行当前被丢弃的任务
DiscardOldestPolicy策略:将任务队列中最老的任务丢弃,尝试再次提交新任务
DiscardPolicy策略:直接丢弃这个无法处理的任务
Executors工具类提供的静态方法返回的线程池默认的拒绝策略是AbortPolicy抛出异常,如果内置的拒绝策略无法满足实际需求,可以拓展RejectedExectionHandler

package com.drhj.thread_pool;

import java.util.Random;
import java.util.concurrent.*;

/**
 * 自定义拒绝策略
 * Author: DRHJ
 * Date: 2022/8/25 21:45
 */
public class Test03 {
    public static void main(String[] args) {
        Runnable r = ()->{
            int num = new Random().nextInt(10);
            System.out.println(Thread.currentThread().getId() + " -- " + System.currentTimeMillis() + "开始睡眠" + "秒");
            try {
                TimeUnit.SECONDS.sleep(num);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10),
                Executors.defaultThreadFactory(), new RejectedExecutionHandler(){
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                //r就是请求的任务,executor就是当前线程池
                System.out.println(r + " is discarding.. ");
            }
        });

        //向线程池提交任务
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            threadPoolExecutor.submit(r);
        }
    }
}

在这里插入图片描述
任务放不下就执行拒绝策略

五,ThreadFactory

线程池中的线程从哪儿来的?答案就是ThreadFactory
ThreadFactory是一个接口,只有一个用来创建线程的方法:

Thread newThread(Runnable r);

当线程池中需要创建线程时就会调用该方法

1,创建线程池

package com.drhj.thread_pool;

import com.drhj.part01.MyThread;

import java.util.Random;
import java.util.concurrent.*;

/**
 * 自定义线程工厂
 * Author: DRHJ
 * Date: 2022/9/13 20:24
 */
public class Test04 {
    public static void main(String[] args) {
        Runnable r = () -> {
            int num = new Random().nextInt(10);
            System.out.println(Thread.currentThread().getId() + "--" + System.currentTimeMillis() + " 开始睡眠" + num + "秒");
            try {
                TimeUnit.SECONDS.sleep(num);
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
        //创建线程池,使用自定义线程工厂,采用默认的拒绝策略
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactory() {

            @Override
            public Thread newThread(Runnable r) {
                //根据参数r任务,创建一个线程
                Thread t = new Thread(r);
                t.setDaemon(true);      //设置守护线程,当主线程运行结束,线程池中的线程会自动退出
                System.out.println("创建了线程: " + t);
                return t;
            }
        });

        //提交5个任务,当给当前线程提交的任务超过5个时,线程池默认抛出异常
        for (int i = 0; i < 5; i++) {
            executorService.submit(r);
        }

        //主线程睡眠
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //主线程睡眠超时时,主线程结束,线程池中的线程会自动退出

    }
}

在这里插入图片描述
当线程超过设置的5时,调用默认拒绝策略:抛错
在这里插入图片描述
在这里插入图片描述

六,监控池线程

ThreadPoolExecutor 提供一组方法用于监控线程池
int getActiveCount() 获得线程池中当前活动线程的数量
long getCompletedTaskCount() 返回线程池完成任务的数量
int getCorePoolSize() 线程池中核心线程的数量
int getLargestPoolSize() 返回线程池曾经达到的线程的最大数
int getMaximumPoolSize() 返回线程池的最大容量
int getPoolSize() 当前线程池的大小
getQueue() 返回阻塞队列
long getTaskCount() 返回线程池收到的任务总数

package com.drhj.thread_pool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Author: DRHJ
 * Date: 2022/10/8 20:39
 * 监控线程池
 */
public class Test05 {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = ()->{
            System.out.println(Thread.currentThread().getId() + " 编号 的线程开始执行: " + System.currentTimeMillis());
            try {
                Thread.sleep(10000);                //线程睡眠20s, 模拟任务执行时长
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 0, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());

        //向线程池提交30个任务
        for (int i = 0; i < 30; i++) {
            pool.submit(r);
            System.out.println("当前线程池核心线程数量: " + pool.getCorePoolSize() + ", 最大线程池: " +
                    pool.getMaximumPoolSize() + ", 当前线程池大小: " + pool.getPoolSize() + ", 活动线程数量: " +
                    pool.getActiveCount() + ", 收到任务数量: " +
                    pool.getTaskCount() + ", 完成任务数: " +pool.getCompletedTaskCount() +
                    ", 等待任务数: " + pool.getQueue().size());
            TimeUnit.MILLISECONDS.sleep(500);
        }

        System.out.println(" ------------------------- ");
        while (pool.getActiveCount() > 0) {
            System.out.println("当前线程池核心线程数量: " + pool.getCorePoolSize() + ", 最大线程池: " +
                    pool.getMaximumPoolSize() + ", 当前线程池大小: " + pool.getPoolSize() + ", 活动线程数量: " +
                    pool.getActiveCount() + ", 收到任务数量: " +
                    pool.getTaskCount() + ", 完成任务数: " +pool.getCompletedTaskCount() +
                    ", 等待任务数: " + pool.getQueue().size());
            Thread.sleep(500);
        }
    }
}

在这里插入图片描述

七,拓展线程池

有时需要对线程池进行拓展,如在监控每个任务的开始和结束时间,或者自定义一些其他增强的功能。
ThreadPoolExecutor线程池提供了两个方法:
protected void afterExecute(Runnable r, Throwable t)
protected void beforeExecute(Thread t, Runnable r)
在线程池执行某个任务前会调用beforeExecute()方法,在任务结束后(任务异常退出)会执行afterExecute()方法。
查看ThreadPoolExecutor源码,在该类中定义了一个内部类Worker,ThreadPoolExecutor线程池中的工作线程就是Worker类的实例,Worker实例在执行时也会调用beforeExecute()与afterExecute()方法

package com.drhj.thread_pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Author: DRHJ
 * Date: 2022/10/9 19:26
 * 拓展线程池
 */
public class Test06 {

    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 t, Runnable r) {
                System.out.println(t.getId() + " 线程准备执行任务: " + ((MyTask)r).name);
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println(((MyTask)r).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.shutdown();     //关闭线程池仅仅是说线程池不再接收新的任务,线程池中已接收的任务正常执行完毕
    }

    //定义任务类
    private static class MyTask implements Runnable {
        public 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();
            }
        }
    }
}

在这里插入图片描述

八,优化线程池数量

线程池大小对系统性能是有一定的影响的,过大或者过小都会无法发挥最优的系统性能,线程池大小不需要非常精确,只要避免极大或者极小的情况即可,一般来说,线程池大小需要考虑CPU数量,内存大小等因素。
线程池大小 = CPU的数量 * 目标CPU的使用率 * (1 + 等待时间与计算时间的比)。

九,线程池死锁

如果在线程池中执行的任务A在执行过程中又向线程池提交了任务B,任务B添加到了线程池的等待队列中,如果任务A的结束需要等待任务B的执行结果,就有可能会出现这种情况:线程池中所有的工作线程都处于等待任务处理结果,而这些任务阻塞队列中等待执行,线程池中没有可以对阻塞队列中的任务进行处理的线程,这种等待会一直持续下去,从而造成死锁。
适合给线程池提交相互独立的任务,而不是彼此依赖的任务。对于彼此依赖的任务,可以考虑分别提交到不同的线程池来执行。

十,线程池中异常

package com.drhj.thread_pool;

import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Author: DRHJ
 * Date: 2022/10/9 21:27
 * 演示线程池可能会吃掉程序中的异常
 */
public class Test07 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0,
                TimeUnit.SECONDS, new SynchronousQueue<>());

        //向线程池中添加任务
        for (int i = 0; i < 5; i++) {
            pool.submit(new DivideTask(10, i));
        }

        /*
            运行程序,只有四条计算结果,我们实际上向线程池提交了5个计算任务,分析结果发现当 i == 0时,提交的任务会产生算数异常,
            但是线程池把该异常给吃掉了,导致我们对异常一无所知
            解决方法:
            1) 把submit()改为execute()
            2) 对线程池进行扩展,对submit()方法进行包装
         */
    }

    //用于计算两个数相除
    private static class DivideTask implements Runnable {
        private int x;
        private int y;

        public DivideTask(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "计算: " + x + " / " + y +  " = " + (x/y));
        }
    }
}

如上代码,当出现异常时线程池会把异常吃掉,我们无法知道发生了异常。
解决方法:

  1. 把submit()改为execute()
  2. 对线程池进行扩展,对submit()方法进行包装

1,submit()方法进行包装来捕获异常

package com.drhj.thread_pool;

import java.util.concurrent.*;

/**
 * Author: DRHJ
 * Date: 2022/10/9 21:59
 * 自定义线程池类,对ThreadPoolExecutor进行拓展
 */
public class Test08 {

    public static void main(String[] args) {
//        ThreadPoolExecutor pool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0,
//                TimeUnit.SECONDS, new SynchronousQueue<>());

        //使用自定义的线程池
        ThreadPoolExecutor pool = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE, 0,
                TimeUnit.SECONDS, new SynchronousQueue<>());

        //向线程池中添加任务
        for (int i = 0; i < 5; i++) {
            pool.submit(new DivideTask(10, i));
        }

        /*
            运行程序,只有四条计算结果,我们实际上向线程池提交了5个计算任务,分析结果发现当 i == 0时,提交的任务会产生算数异常,
            但是线程池把该异常给吃掉了,导致我们对异常一无所知
            解决方法:
            1) 把submit()改为execute()
            2) 对线程池进行扩展,对submit()方法进行包装
         */
    }

    //用于计算两个数相除
    private static class DivideTask implements Runnable {
        private int x;
        private int y;

        public DivideTask(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "计算: " + x + " / " + y +  " = " + (x/y));
        }
    }

    private static class TraceThreadPoolExecutor extends ThreadPoolExecutor {
        public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }

        //定义方法,对执行的任务进行包装,接收两个参数,第一个参数接收要执行的任务, 第二个参数是一个Exception异常
        public Runnable wrap(Runnable task, Exception exception) {
            return new Runnable() {
                @Override
                public void run() {
                    try {
                        task.run();
                    } catch (Exception e) {
                        exception.printStackTrace();
                        throw e;
                    }
                }
            };
        }

        //重写submit方法

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

        @Override
        public void execute(Runnable command) {
            super.execute(wrap(command, new Exception("客户跟踪异常")));
        }
    }
}

在这里插入图片描述

十一,ForkJoinPool线程池

“分而治之”是一个有效的处理大数据的方法,著名的MapReduce就是采用这种分而治之的思路。简单点说,如果要处理的1000个数据,但是我们不具备处理1000个数据的能力,可以只处理10个数据,可以把这1000个数据分阶段处理100次,每次处理10个,把100次的处理结果进行合成,形成最后这1000个数据的处理结果。
把一个大任务调用fork()方法分解为若干小的任务,把小任务的处理结果进行join()合并成大的结果。
系统对ForkJoinPool线程池进行了优化,提交的任务数量与线程的数量不一定是一对一关系。在多数情况下,一个物理线程实际上需要处理多个逻辑任务。
如:
线程A把自己的任务执行完毕
线程B的任务队列中海油若干的任务等待执行
线程A会从线程B的等待队列中取任务帮助线程B完成
线程A在帮助线程B执行任务时,总是从线程B的等待队列底部开始取任务

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

package com.drhj.thread_pool;

import java.util.ArrayList;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

/**
 * Author: DRHJ
 * Date: 2022/10/10 20:21
 * 演示ForkJoinPool线程池的使用
 *  使用该线程池模拟数列求和
 */
public class Test09 {
    public static void main(String[] args) {
        //创建线程池
        ForkJoinPool pool = new ForkJoinPool();
        //创建大的任务
        CountTask task = new CountTask(0L, 200000L);
        //把大任务提交给线程池
        ForkJoinTask<Long> result = pool.submit(task);
        try {
            Long res = result.get();            //调用任务的get()方法返回结果
            System.out.println("计算数列结果为: " + res);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //计算数列的和,需要返回结果,可以定义任务继承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;     //计算数列的起始值
        private long end;       //计算数列的结束值

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

        //重写,计算数列的结果
        @Override
        protected Long compute() {
            long sum = 0;       //保存计算的结果
            //判断任务是否需要继续分解,如果当前数列end与start范围的数超过阈值THRESHOLD,就需要继续分解
            if (end - start < THRESHOLD) {
                //小于阈值可以直接计算
                for (long i = start; i <= end; i++) {
                    sum += i;
                }
            } else {        //数列范围超过阈值,需要继续分解
                //约定每次分解成100个小任务,计算每个任务的计算量
                long step = (start + end) / TASKNUM;
                //start = 0, end = 200000, step = 2000, 如果计算[0, 200000]范围内数列的和,把该范围的数列分解为100个小任务,每个任务计算2000个数即可
                //注意,如果任务划分的层次很深,即THRESHOLD阈值太小,每个任务的计算量很少,层次划分就会很深,可能出现两种情况:一是系统内的线程数量会越积越多,
                //导致性能下降严重;二是分解次数过多,方法调用过多可能会导致栈溢出。
                //创建一个存储任务的集合
                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);
                    //调用fork()提交任务
                    task.fork();
                    //调整下个任务的起始位置
                    pos += step + 1;
                }
                //等待所有的子任务结束后,合并计算结果
                for (CountTask task : subTaskList) {
                    sum += task.join();   //join()会一直等待子任务执行完毕返回执行结果
                }
            }
            return sum;
        }
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值