并发编程-并发工具类(一)

Fork-Join

相对于显示去new 一个线程(使用Thread)其实我们在实际工作中更多的使用线程池。当然除了线程池还可以使用forkjoin。只要我们遵循了forkjoin的开发模式,就可以写出很好的多线程并发程序。

刚开始使用forkjoin 的时候其实有个疑问,就是javaa 除了提供了ThreadPoolExecutor 之外还提拱了ForkJoinPool,这个类跟ThreadPoolExecutor 类大体相同,那么实际应用中该如何选择呢?

其实这两个池都是实现ExecutorService,说白了其实是同源的,那有啥区别呢?

ThreadPoolExecutor 是单纯的线程池,更像是通用。

ForkJoinPool 这个是一个封装了分而治之玩法的工具类。使用的范围不一样,是为需要多线程分治提供的;

那我们怎么使用fork-join 呢?
其实fork-join 也跟wait/notity 一样有基本的使用范式


import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.RecursiveTask;

/**
 * fork-join 使用范式演示
 * 
 * @author ckj
 *
 */
public class ForkJoinPoolDemo {

	/*
	 * 使用fork-join时不需要返回值继承RecursiveAction类 需要实现compute 方法判断任务是否足够小, 如果 足够小就直接执行任务。
	 * 如果不足够小,就必须分割成两个子任务,每个子任务 在调用 invokeAll 方法时, 又会进入compute 方法, 看看当前子任务是否需要继
	 * 续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。 使用 join方法会等待子任务执行完并得到其结果。
	 */
	static class MyForkJoinAction extends RecursiveAction {

		@Override
		protected void compute() {
			

		}

	}

	/*
	 * 使用fork-join时需要返回值继承MyForkJoinTask类。 需要实现compute 方法判断任务是否足够小, 如果 足够小就直接执行任务。
	 * 如果不足够小,就必须分割成两个子任务,每个子任务 在调用 invokeAll 方法时, 又会进入compute 方法, 看看当前子任务是否需要继
	 * 续分割成孙任务,如果不需要继续分割,则执行当前子任务并返回结果。 使用 join方法会等待子任务执行完并得到其结果。
	 */
	static class MyForkJoinTask extends RecursiveTask<String> {

		@Override
		protected String compute() {
			
			return "hello fork/join";
		}

	}

	public static void main(String[] args) {

		ForkJoinPool pool = new ForkJoinPool();
		/*
		 * 通常需要定义一个ForkJoin任务,提供了fork 和join 的工作机制
		 * 通常我们不使用ForkJoinTask,而是直接使用他的子类
		 * 1、RecursiveAction 用户没有返回结果的任务
		 * 2、RecursiveTask 用于有返回值的任务
		 * 
		 */
		// 无返回值
		MyForkJoinAction action = new MyForkJoinAction();
		// 有返回值
		MyForkJoinTask task = new MyForkJoinTask();
		
		pool.submit(task);
		
		//获得结果 只有任务定义为RecursiveTask 才有返回结果
		String result = task.join();
		System.out.println(result);

	}

看到代码我们其实知道了,其实使用forkjoin 的话重点需要放在compute() 方法中,只有写好compute()我们才能真正使用好forkjoin。

其实想使用好forkjoin 需要好好了解下递归的压栈与出栈,可以去网上找找归并排序的写法。也可以通过开发工具(这边使用SpringTools)打开debug 视图一步步跟踪去查看压栈与出栈的过程。

下面演示使用forkjoin 统计文件数量:

1、使用单线程统计

import java.io.File;

/**
 * 单线寻找所有文件
 * 
 * @author ckj
 *
 */
public class SingleFindFile {

	private static int findFile(File fileDir,int num) {
		File[] files = fileDir.listFiles();
	
		if (files != null) {
			for (File file : files) {
				if (file.isDirectory()) {
					// 对每个子目录都新建一个子任务。
					num = findFile(file,num);
				} else {
					// 遇到文件,检查。
					if (file.getAbsolutePath().endsWith("txt")) {
					//	System.out.println("文件:" + file.getAbsolutePath());
						num++;
					
					}
				}
			}

		}
		return num;

	}

	public static void main(String[] args) {
		File fileDir = new File("D:/");
		long start = System.currentTimeMillis();
		int num =findFile(fileDir,0);
		long end = System.currentTimeMillis();
		System.out.println("单线程查询所有txt文件耗时:"+(end-start)+"文件数量为:"+num);
	}

单线程查询所有txt文件耗时:20246文件数量为:2596

2、forkjoin统计


import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

/**
 * 通过ForkJoin 获取指定目录下所有的文件夹
 * 
 * @author ckj
 *
 */
public class ForkJoinFindFile {

	/**
	 * 定义一个有返回值的
	 * 
	 * @author asus
	 *
	 */
	static class ForkJoinTask extends RecursiveTask<List<String>> {

		private File fileDir;

		public ForkJoinTask(File file) {
			this.fileDir = file;
		}

		@Override
		protected List<String> compute() {
			List<ForkJoinTask> subTasks = new ArrayList<ForkJoinTask>();
			List<String> result = new ArrayList<String>();
			File[] files = fileDir.listFiles();
			if (files != null) {
				for (File file : files) {
					if (file.isDirectory()) {
						// 对每个子目录都新建一个子任务。
						subTasks.add(new ForkJoinTask(file));
					} else {
						// 遇到文件,检查。
						if (file.getAbsolutePath().endsWith("txt")) {
							result.add(file.getAbsolutePath());
							// System.out.println("文件:" + file.getAbsolutePath());
						}
					}
				}

				if (!subTasks.isEmpty()) {
					// 在当前的 ForkJoinPool 上调度所有的子任务。
					Collection<ForkJoinTask> invokeAll = invokeAll(subTasks);
					for (ForkJoinTask subTask : invokeAll) {
						result.addAll(subTask.join());
					}
				}

			}
			return result;
		}

	}

	public static void main(String[] args) {
		long start = System.currentTimeMillis();
		ForkJoinPool pool = new ForkJoinPool();
		// 传入文件夹路径
		ForkJoinTask task = new ForkJoinTask(new File("D:/"));
		pool.submit(task);
		List<String> join = task.join();
		long end = System.currentTimeMillis();
		System.out.println("forkJoin查询所有txt文件耗时:" + (end - start) + "  查询数量为:" + join.size());

	}

}

forkJoin查询所有txt文件耗时:6265  查询数量为:2596

可以看到效率明显提升了。

ForkJoinPool 既然是个线程池,他定义的线程数量时多少?

这边使用Java 虚拟机线程系统的管理接口来查看下,将main 方法修改成如下所示

public static void main(String[] args) {
		//Java 虚拟机线程系统的管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        
		long start = System.currentTimeMillis();
		ForkJoinPool pool = new ForkJoinPool();
		// 传入文件夹路径
		ForkJoinTask task = new ForkJoinTask(new File("D:/"));
		pool.submit(task);
		List<String> join = task.join();
		 // 不需要获取同步的monitor和synchronizer信息,仅仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] "
                    + threadInfo.getThreadName());
        }
		
		long end = System.currentTimeMillis();
		System.out.println("forkJoin查询所有txt文件耗时:" + (end - start) + "  查询数量为:" + join.size());

	}

打印结果

[21] ForkJoinPool-1-worker-0
[20] ForkJoinPool-1-worker-7
[19] ForkJoinPool-1-worker-6
[18] ForkJoinPool-1-worker-5
[17] ForkJoinPool-1-worker-4
[16] ForkJoinPool-1-worker-3
[15] ForkJoinPool-1-worker-2
[14] ForkJoinPool-1-worker-1
[8] JDWP Command Reader
[7] JDWP Event Helper Thread
[6] JDWP Transport Listener: dt_socket
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main

可以看到有8个ForkJoinPool worker 线程,那么他是不是一个固定值呢?

查看ForkJoinPool的构造方法:

public ForkJoinPool() {
        this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()),
             defaultForkJoinWorkerThreadFactory, null, false);
    }

发现了Runtime.getRuntime().availableProcessors() ,此方法是获取cpu核心线程数,所以在我的电脑上是8。那Doug Lea为什么要这样设计呢?

这边就提到了一个线程池定义核心线程数的业内参考方法了

1、cpu密集型(多复杂计算,逻辑处理):核心线程数设置为cpu核心线程数 ,只是一个参考值

2、io密集型(cpu 使用底程序中会存在大量I/O操作):((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目 ,但是我一般设置为  cpu核心线程数*2 

3、复合型:这个没有参考值,需要压力测试来辅助测试

上述的都是参考值而已,一般是需要压力测试和业务分析后来设置的。

forkjoin还有一个非常重要的一点:工作密取

即当前线程的 Task 已经全被执行完毕,则自动取到其他线程的 Task 池中取 出 Task 继续执行。

概念的东西不想说太多, 知道有这个东西就可以。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值