使用fork/join框架代替执行器

本文介绍Java9中fork/join框架的使用,通过对比传统执行器,展示其在处理可分割问题上的性能优势。通过具体实例,包括TaskFJ类和Task类的实现,以及ForkJoinPool和ThreadPoolExecutor的性能比较。

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

Java 9并发编程指南 目录

使用fork/join框架代替执行器

使用执行器无需创建和管理线程,只要通过实现Runnable或Callable接口创建任务,并将任务发送给执行器,其线程池会分配一个线程来执行任务。

Java 7引入fork/join框架的执行器,此框架在ForkJoinPool类中实现,使用分治技术将问题分割成更小的部分。当为fork/join框架实现任务时,需要检查待解决问题的大小。如果大于预定义值,则将问题划分为两个或多个子集,并创建与所划分数量相同的子任务。任务使用fork()操作发送这些子任务到ForkJoinPool类,并使用join()操作等待任务结束。

对于这类问题,fork/join池的性能要优于传统执行器。本节将实现范例来验证这个观点。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为TaskFJ的类,指定其继承RecursiveAction类:

    public class TaskFJ extends RecursiveAction{
    
  2. 声明名为array的私有整型数组:

    	private final int array[];
    
  3. 声明名为start和end的私有整型属性:

    	private final int start, end;
    
  4. 实现类构造函数,初始化属性:

    	public TaskFJ(int array[], int start, int end) {
    		this.array=array;
    		this.start=start;
    		this.end=end;
    	}
    
  5. 实现compute()方法。如果此任务需要处理超过1000个元素(由开始和结束属性决定)的块,则创建两个

    TaskFJ对象,使用fork()方法将它们发送到ForkJoinPool类,使用join()方法等待任务执行结束:

    	@Override
    	protected void compute() {
    		if (end-start>1000) {
    			int mid=(start+end)/2;
    			TaskFJ task1=new TaskFJ(array,start,mid);
    			TaskFJ task2=new TaskFJ(array,mid,end);
    			task1.fork();
    			task2.fork();
    			task1.join();
    			task2.join();
    
  6. 反之,递增此任务需要处理的元素。每次递增操作之后,设置线程休眠1毫秒:

		} else {
			for (int i=start; i<end; i++) {
				array[i]++;
				try {
					TimeUnit.MILLISECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}		
	}
}
  1. 创建名为Task的类,指定其实现Runnable接口:

    public class Task implements Runnable{
    
  2. 声明名为array的私有整型数组:

	private final int array[];
  1. 实现类构造函数,初始化属性:

    	public Task(int array[]) {
    		this.array=array;
    	}
    
  2. 实现run()方法。递增数组中的所有元素,每次递增操作之后,设置线程休眠1毫秒:

    	@Override
    	public void run() {
    		for (int i=0; i<array.length; i++ ){
    			array[i]++;
    			try {
    				TimeUnit.MILLISECONDS.sleep(1);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
  3. 实现本范例主类,创建名为Main的类,包含main()方法:

    public class Main {
    	public static void main(String[] args) {
    
  4. 创建包含100000个元素的整型数组:

    		int array[]=new int[100000];
    
  5. 创建Task对象和ThreadPoolExecutor对象,然后执行。通过控制任务运行的时间来执行任务:

    		Task task=new Task(array);
    		ExecutorService executor=Executors.newCachedThreadPool();
    		Date start,end;
    		start=new Date();
    		executor.execute(task);
    		executor.shutdown();
    		try {
    			executor.awaitTermination(1, TimeUnit.DAYS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		
    		end=new Date();
    		System.out.printf("Main: Executor: %d\n", (end.getTime()-start.getTime()));
    
  6. 创建TaskFJ对象和ThreadPoolExecutor对象,然后执行。通过控制任务运行的时间来执行任务:

    		TaskFJ taskFJ=new TaskFJ(array,1,100000);
    		ForkJoinPool pool=new ForkJoinPool();
    		start=new Date();
    		pool.execute(taskFJ);
    		pool.shutdown();
    		try {
    			pool.awaitTermination(1, TimeUnit.DAYS);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		end=new Date();
    		System.out.printf("Core: Fork/Join: %d\n", (end.getTime()-start.getTime()));
    	}
    }
    

工作原理

当执行本范例时,会看到ForkJoinPool和TaskFJ类的性能是如何优于ThreadPoolExecutor和Task类的。

如果需要解决能够使用分治技术进行分割的问题,使用ForkJoinPool类代替ThreadPoolExecutor类,性能会更好。

扩展学习

  • 本章“将线程管理委托给执行器”小节
1.编写C程序,初始化一个count变量值为1,然后使用fork函数创建两个子进程,在父进程和子女进程中分别对count变量加1,并分别打印“I am father/son/daughter,count=X”,X是count的值,即分别打印“我是谁和count的值”,最后父进程使用waitpid等待两个子进程结束后退出。2.编译后,多次运行该程序,观察屏幕上显示结果的顺序性,直至出现不一样的情况为止,记下这种情况,并观察每行打印结果中count的值,试简单分析其原因。 3.改写前面的程序,用pthread_create函数代替fork使父进程创建两个线程,用pthread_join函数代替waitpid使父进程等待两个线程结束后退出,其他不变。4.编译链接后,多次运行该次序,观察屏幕上显示结果的顺序性,直至出现不一样的情况为止,记下这种情况,并观察每行打印结果中count的值,试简单分析其原因。5.在Linux平台上编写一段长循环程序,通过ps命令查看进程“运行”状态;6.向进程发送SIGSTOP信号,通过ps命令查看进程的“暂停”状态;向进程发送SIGCONT信号,通过ps命令查看进程恢复到“运行”状态;7.在Linux平台上编写调用sleep函数程序,通过ps查看进程“可中断阻塞”状态;8.在Linux平台上使用vfork创建子进程,通过ps查看主进程“不可中断阻塞”状态;9.在Linux平台上使用fork创建一个子进程,子进程不执行任何操作,而父进程调用sleep函数进入睡眠状态,通过ps命令查看父进程进入“可中断阻塞”状态,子进程进入“僵尸”状态;10.通过以上程序结果,结合资料查询,画出Linux下进程状态的转换图,并思考什么时候出现运行状态、暂停状态、可中断阻塞状态、不可中断阻塞状态以及僵尸状态。 解答一下第十题
05-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值