利用线程池加速程序运行:从单线程948ms到多线程276ms的效率提升
内容简介

我最近与之接触的项目具有荒谬的数据,并且某些复杂的算法功能只是一场灾难。事不宜迟,以下是用于使用线程池来加快程序操作的示例参考。欢迎大家讨论并纠正它。
结果
直接将图片放置:
计算结果是相同的。计算时间仅为948ms。计算时间为276ms,使用4个线程池的螺纹,该线程的速度快四次甚至三倍!
完成代码
public static void main(String[] args) throws ParseException, InterruptedException, ExecutionException {
// 初始化一个巨大数组,可以想象为我们需要处理的业务数据,上万甚至上亿条记录
int[] a = new int[360000];
// 随机放入100内随机数
rand(a);
long beginTime = System.currentTimeMillis();
long b = 0L;
// 传统单线程计算,10000*360000次运算,模拟对业务数据的处理过程
for (int k = 0; k<10000 ; k++) {
for (int i : a) {
b += i;
}
}
System.out.println("总和"+b);
System.out.println("单独计算耗时"+(System.currentTimeMillis()-beginTime)+"毫秒");
System.out.println("_______________________________________________________");
// 使用线程池计算
beginTime = System.currentTimeMillis();
// 定义线程池,各参数含义可以看一下官方文档说明
ExecutorService pool = new ThreadPoolExecutor(4,8,6
,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
// 将业务数据切割为4份,分为4个线程处理,实际使用中可能不清楚数据量,用for循环就好
// 此处子线程可用runable或callable,如果需要回调信息,则必须使用callable,返回值在自定义的线程类中编写
Callable<Long> runnable = new sumThread(a,0,90000);
Callable<Long> runnable1 = new sumThread(a,90000,180000);
Callable<Long> runnable2 = new sumThread(a,180000,270000);
Callable<Long> runnable3 = new sumThread(a,270000,360000);
System.out.println("准备启动");
// 四个线程初始化完成,提交给线程池
Future<Long> num = pool.submit(runnable);
Future<Long> num1 = pool.submit(runnable1);
Future<Long> num2 = pool.submit(runnable2);
Future<Long> num3 = pool.submit(runnable3);
System.out.println("启动完成");
// 调用callable的get()方法,从各子线程获取返回值,由于callable.get()方法特性,子线程运行完毕后才会调用这一行
System.out.println("总和"+(num.get()+num1.get()+num2.get()+num3.get()));
long endTime = System.currentTimeMillis();
System.out.println("线程池总耗时"+(endTime-beginTime)+"毫秒");
//关闭线程池,已提交的任务继续执行,不再接收新的任务
pool.shutdown();
//等待所有的任务都结束(实时判断是否全完成),若所有任务都已完成,则返回true,若超时未完成,则返回false
if (!pool.awaitTermination(6, TimeUnit.SECONDS)) {
//超时的时候向线程池中所有的线程发出中断(interrupted)。
pool.shutdownNow();
}
}
// 向数组中填充100内随机数
private static void rand(int[] a) {
for (int i = 0; i < a.length; i++) {
a[i] = new Random().nextInt(100);
}
}
// 编写自定义线程类,可以实现runable或callable,如需调用返回值,则必须实现callable
class sumThread implements Callable<Long> {
// 存放的业务数据
int[] a ;
// 定义一个返回值,可按需求修改
long result;
// 初始化线程实例时,从业务数据中根据下标取出 一小段数据
public sumThread(int[] b,int start,int end) {
a=Arrays.copyOfRange(b,start,end);
System.out.println("初始化一个线程实例");
}
// 执行方法,模拟对业务数据的处理过程,运行完成后return指定的返回值
@Override
public Long call() throws Exception {
System.out.println("启动一个线程");
// a = rand1(a);
long beginTime = System.currentTimeMillis();
long b = 0L;
for (int k = 0; k<10000 ; k++) {
for (int j = 0; j < a.length; j++) {
b += a[j];
}
}
long endTime = System.currentTimeMillis();
System.out.println("线程耗时"+(endTime-beginTime)+"毫秒");
System.out.println("线程计算结果"+b);
System.out.println("结束一个线程");
result = b;
return result;
}
}
为什么线程池更快?
线程池提高程序操作效率的原理相对复杂。类比,如果将CPU核心与程序员进行比较(SAD),则单线线程就像程序员编写代码一样,而另一个项目经理和程序员正在喝茶和钓鱼。比较多线程,一位项目经理指示n个程序员同时编写代码。尽管项目经理(主线程)需要一些时间来创建工作站,举行会议来解释任务,并监督和管理项目进度,但n个人共同努力,比一个人单独进行项目更快。这也是线程处理时间的四个原因是单线处理时间的四分之一。 (当多汁的人工作时,他们也应该及时沟通。三个补鞋匠是最好的Zhuge Liang,而三位程序员至少是最佳的Feng Neumann)这也是“软件工程”的想法之一。您可以在日常生活中对此进行更多的思考。 “工程”的含义。
在实际的业务场景中,它比这要复杂得多,例如在处理业务数据后调用接口或从磁盘读取数据。 CPU速度比磁盘速度和网络速度快得多,这将导致程序的实际计算时间比空闲等待时间小得多。 CPU大部分时间都在吸吮。
另外,我想以程序员为例。小敏收到了一个工作单并写了业务功能A。完成代码(CPU操作)和5分钟的时间来编译和打包项目(检索接口)(有点夸张,但从计算机的角度来看, CPU计算速度比网络速度快得多),小敏基本上是在接下来的5分钟内等待茶。当然,我们希望小敏在接下来的5分钟内有事要做。第二天,小明将两台计算机放在他面前,并在汇编业务函数A时编写了业务功能B的守则B,并一遍又一遍地重复,这大大提高了时间利用率。
为什么建议将线程池用于多线程?每次直接写线程对我来说是否可以?
从功能实施的角度来看,这不是不可能的。但是据说,项目经理为程序员安排了任务,需要为他们提供工作站(创建新线程)。程序员的代码完成后,项目经理将拆除工作站(SAD),创建新的工作站和拆除工作站将消耗不必要的资源。线程池将保留这些空的工作站。线程池参数中的corepoolsize驻留核心线程定义了可以保留的电台数量。完成工作后,程序员仍将保留工作站。下次将有一项任务。只需坐下来工作以节省资源。通过调整线程池的其他参数,可以执行越来越多的精制调整和控制多线程程序。
使用多线程的目的是使程序更快并拥有更好的体验。由于使用线程池可以进一步节省资源并提高速度,为什么不呢?请记住,使用线池后,只需卸下不再使用的电台即可。
多线程随时更快吗?我可以没有大脑使用它吗?
当然,这不是,因为线程池需要管理和安排每个子线程,这也将消耗一定的时间和资源。如果数据量很小并且计算很简单,则使用多个线程实际上将拖动程序的运行速度。此外,无需使用如此多的技术将其通过简单的小数据功能(写代码也很累)来调整,这也是直觉。
当然,多线程用法方案仅限于不影响业务数据之间处理的方案:处理任何部分业务数据的处理不取决于其他部分的处理结果,或者没有严格的前端和 - 返回计算关系。就个人而言,我们倾向于对大量数据进行更重复和简单的操作。