Java 并发框架Executor 和Fork-Join

本文探讨了在面对大量任务和数据时,如何从串行编程转向并行计算以提高性能。Java提供的Executor框架和Fork-Join框架为并行编程提供了解决方案。Executor框架通过分离任务创建和执行,实现线程复用,避免了频繁创建线程的开销。Fork-Join框架则采用分治策略,适用于任务量不确定但可分解的情况,通过RecursiveAction和RecursiveTask进行任务分解与合并。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并行计算

  • 业务:任务多,数据量大
  • 串行,并行
    – 串行编程简单,并行编程困难
    – 单个计算核频率下降,计算核数增多,整体性能变高(需要选择并行)
  • 并行困难(任务分配和执行过程的高度耦合
    – 如何分配粒度,切割任务
    – 如何分配任务给线程,监督线程执行过程
  • 并行模式
    – 主从模式(Master-Slave)(主线程指挥副线程)
    – Worker模式(Worker-Worker)(所有线程都是平等的)
  • Java并发编程
    – Thread/Runnable/Thread组管理
    – Executor框架
    – Fork-Join框架

线程组管理

线程组ThreadGroup

  • 线程的集合
  • 树形结构,大线程组可以包括小线程组
  • 可以通过enumerate方法遍历组内的线程,执行操作
  • 能够有效管理多个线程,但是管理效率低
  • 任务分配和执行过程的高度耦合 没有解决
  • 重复创建线程,关闭线程操作,无法重用线程(线程new出来后,只能start一次,不能再次start)

activeCount返回线程组中还处于active的线程数(估计数)
enumerate将线程组中active的线程拷贝到数组中
interrupt对线程组所有的线程发出interrupt信号
list打印线程组中所有的线程信息

public class Main {

	public static void main(String[] args) {

		// 创建线程组
		ThreadGroup threadGroup = new ThreadGroup("Searcher");
		Result result=new Result();

		// 创建一个任务,10个线程完成
		Searcher searchTask=new Searcher(result);
		for (int i=0; i<10; i++) {
			Thread thread=new Thread(threadGroup, searchTask);
			thread.start();
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("========华丽丽0=======");
		
		// 查看线程组消息
		System.out.printf("active 线程数量: %d\n",threadGroup.activeCount());
		System.out.printf("线程组信息明细\n");
		threadGroup.list();
		System.out.println("========华丽丽1=======");

		// 遍历线程组
		Thread[] threads=new Thread[threadGroup.activeCount()];
		threadGroup.enumerate(threads);
		for (int i=0; i<threadGroup.activeCount(); i++) {
			System.out.printf("Thread %s: %s\n",threads[i].getName(),threads[i].getState());
		}
		System.out.println("========华丽丽2=======");

		// Wait for the finalization of the Threadds
		waitFinish(threadGroup);  //等至少一个线程结束
		
		// Interrupt all the Thread objects assigned to the ThreadGroup
		threadGroup.interrupt();//向线程组里所有线程发送停止信号
	}

	public static void waitFinish(ThreadGroup threadGroup) {
		while (threadGroup.activeCount()>9) {
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class Searcher implements Runnable {

	private Result result;
	
	public Searcher(Result result) {
		this.result=result;
	}

	@Override
	public void run() {
		String name=Thread.currentThread().getName();
		System.out.printf("Thread %s: 启动\n",name);
		try {
			doTask();
			result.setName(name);
		} catch (InterruptedException e) {
			System.out.printf("Thread %s: 被中断\n",name);
			return;
		}
		System.out.printf("Thread %s: 完成\n",name);
	}
	
	private void doTask() throws InterruptedException {  //睡眠随机时间
		Random random=new Random((new Date()).getTime());
		int value=(int)(random.nextDouble()*100);
		System.out.printf("Thread %s: %d\n",Thread.currentThread().getName(),value);
		TimeUnit.SECONDS.sleep(value);
	}
}

Executor 框架

  • 从JDK 5 开始提供Executor Frame Work(java.util.concurrent.
    – 分离任务的创建和执行者的创建
    – 线程重复利用(new 线程的代价很大)

  • 理解共享线程池的概念
    – 预设好多个Thread,可弹性增加
    – 多次执行很多很小的任务
    – 任务创建和执行过程解耦
    程序员无需关心线程池执行任务过程

  • 主要类:
    Executors.newCachedThreadPool/newFixedThreadPool创建线程池
    ExecutorService 线程池服务
    Callable 具体的逻辑对象(线程类) 是接口 和Runnable等价,里面的call方法类似run方法,但是是可以有返回值的
    Future 返回结果

public class Main {

	public static void main(String[] args) throws InterruptedException {
		// 创建一个执行服务器
		Server server=new Server();
		
		// 创建100个任务,并发给执行器,等待完成
		for (int i=0; i<100; i++){
			Task task=new Task("Task "+i);  //自动来启动线程池里的线程数,进行计算任务
			Thread.sleep(10);
			server.submitTask(task);  //给线程池任务
		}		
		server.endServer();  //关闭线程池
	}
}

/**
 * 执行服务器
 *
 */
public class Server {
	
	//线程池
	private ThreadPoolExecutor executor;
	
	public Server(){
		executor=(ThreadPoolExecutor)Executors.newCachedThreadPool(); //线程池容量自动增长
		//executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(5);
	}
	
	//向线程池提交任务
	public void submitTask(Task task){
		System.out.printf("Server: A new task has arrived\n");
		executor.execute(task); //执行  无返回值
		System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
		System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
		System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());
	}

	public void endServer() {
		executor.shutdown();
	}
}
/**
 * Task 任务类
 * @author Tom
 *
 */
public class Task implements Runnable {

	private String name;
	
	public Task(String name){
		this.name=name;
	}
	
	public void run() {
		try {
			Long duration=(long)(Math.random()*1000);
			System.out.printf("%s: Task %s: Doing a task during %d seconds\n",Thread.currentThread().getName(),name,duration);
			Thread.sleep(duration);			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.printf("%s: Task %s: Finished on: %s\n",Thread.currentThread().getName(),name,new Date());
	}

}

	

计算总和的例子

public class SumTest {

	public static void main(String[] args) {
		
		// 执行线程池
		ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(4);
		
		List<Future<Integer>> resultList=new ArrayList<>();

		//统计1-1000总和,分成10个任务计算,提交任务
		for (int i=0; i<10; i++){
			SumTask calculator=new SumTask(i*100+1, (i+1)*100);  
			Future<Integer> result=executor.submit(calculator);
			resultList.add(result);
		}
		
		// 每隔50毫秒,轮询等待10个任务结束
		do {
			System.out.printf("Main: 已经完成多少个任务: %d\n",executor.getCompletedTaskCount());
			for (int i=0; i<resultList.size(); i++) {
				Future<Integer> result=resultList.get(i);
				System.out.printf("Main: Task %d: %s\n",i,result.isDone());  //resul.isDone方法,如果这个线程已经被线程池计算出结果了,就true,不然false
			}
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} while (executor.getCompletedTaskCount()<resultList.size());
		
		// 所有任务都已经结束了,综合计算结果		
		int total = 0;
		for (int i=0; i<resultList.size(); i++) {
			Future<Integer> result=resultList.get(i);
			Integer sum=null;
			try {
				sum=result.get();
				total = total + sum;
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		}
		System.out.printf("1-1000的总和:" + total);
		
		// 关闭线程池
		executor.shutdown();
	}
}


public class SumTask implements Callable<Integer> {
	//定义每个线程计算的区间
	private int startNumber;
	private int endNumber;
	
	public SumTask(int startNumber, int endNumber){
		this.startNumber=startNumber;
		this.endNumber=endNumber;
	}
	
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for(int i=startNumber; i<=endNumber; i++)
		{
			sum = sum + i;
		}
		
		Thread.sleep(new Random().nextInt(1000));
		
		System.out.printf("%s: %d\n",Thread.currentThread().getName(),sum);
		return sum;
	}
}

其中我们发现,result中有result.isDone()方法,用来查看当前线程是否已经计算结束出结果,是返回true。还有多出了Callable类,类似Runnable类,也是接口。里面实现的是call方法,可以有返回值。而Runable类中run方法没有返回值。

Fork-Join框架

  • Java 7 提供另一种并行框架:分解,治理,合并(分治编程)
  • 适用于整体任务量不好确认的场合(最小任务可确定
  • 关键类
    – ForkJoinPool 任务池
    – RecursiveAction
    – RecursiveTask
public class SumTest {
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建执行线程池
    	ForkJoinPool pool = new ForkJoinPool();
    	//ForkJoinPool pool = new ForkJoinPool(4);
    	
    	//创建任务
        SumTask task = new SumTask(1, 10000000);
        
        //提交任务
        ForkJoinTask<Long> result = pool.submit(task);
        
        //等待结果
        do {
			System.out.printf("Main: Thread Count: %d\n",pool.getActiveThreadCount());
			System.out.printf("Main: Paralelism: %d\n",pool.getParallelism());
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} while (!task.isDone());  //这里怎么是task.isDone 不是resul.isDone
        
        //输出结果
        System.out.println(result.get().toString());
    }
}

//分任务求和
public class SumTask extends RecursiveTask<Long> {
	
	private int start;
	private int end;

	public SumTask(int start, int end) {
		this.start = start;
		this.end = end;
	}

	public static final int threadhold = 5;

	@Override
	protected Long compute() {
		Long sum = 0L;
		
		// 如果任务足够小, 就直接执行
		boolean canCompute = (end - start) <= threadhold;
		if (canCompute) {
			for (int i = start; i <= end; i++) {
				sum = sum + i;				
			}
		} else {
			// 任务大于阈值, 分裂为2个任务
			int middle = (start + end) / 2;
			SumTask subTask1 = new SumTask(start, middle);
			SumTask subTask2 = new SumTask(middle + 1, end);

			invokeAll(subTask1, subTask2); //这俩子任务 提交了线程池,继续分解

			Long sum1 = subTask1.join(); //等待俩子任务完成
			Long sum2 = subTask2.join();

			// 结果合并
			sum = sum1 + sum2;
		}
		return sum;
	}
}

这里Fork-Join框架,每次是提交一次任务。在这一次任务中,会分解成多个小任务,然后小任务继续分解,直到达到条件。然后结果在一层一层回来,类似于递归。

这里中,这里的任务,没有定义是线程类。???
然后result.isDone(),这里是task.isDone(),???

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值