线程池ThreadPoolExecutor与ForkJoinPool

一、ThreadPoolExecutor

构造器中各个参数的含义:

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0

在ThreadPoolExecutor类中有几个非常重要的方法:

  1. execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
  2. submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
  3. shutdown()和shutdownNow()是用来关闭线程池的。
  4. 比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法

ThreadPoolExecutor简单示例

package constant;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadTest {
	public static void main(String[] args) throws InterruptedException {   
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 200, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
         long s1 = System.currentTimeMillis();
        // 计数控制
        final CountDownLatch latch = new CountDownLatch(330);
        for(int i=0;i<330;i++){
        	// 自定义执行体
            MyTask myTask = new MyTask(i,latch);
            executor.execute(myTask);
            System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
            executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
        }
        // 控制所有线程全部跑完(异步执行可以去掉)
        latch.await();
        long s2 = System.currentTimeMillis();
        long s =s2-s1;
        System.out.println("线程池执行时间:"+s); 
        System.out.println("执行完毕=============================");
        executor.shutdown();
    }
}


class MyTask implements Runnable {
   private int taskNum;
   private CountDownLatch latch;
    
   public MyTask(int num,CountDownLatch latch) {
       this.taskNum = num;
       this.latch = latch;
   }
    
   @Override
   public void run() {
       System.out.println("正在执行task "+taskNum);
       try {
           Thread.currentThread().sleep(10);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }finally {
    	   latch.countDown();
       }
       System.out.println("task "+taskNum+"执行完毕");
   }
}

二、ForkJoinPool

Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。Fork/Join框架要完成两件事情:

  1.任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割

  2.执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。

  在Java的Fork/Join框架中,使用两个类完成上述操作

  1.ForkJoinTask:我们要使用Fork/Join框架,首先需要创建一个ForkJoin任务。该类提供了在任务中执行fork和join的机制。通常情况下我们不需要直接集成ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了两个子类:

    a.RecursiveAction:用于没有返回结果的任务

    b.RecursiveTask:用于有返回结果的任务

  2.ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行

  任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务(工作窃取算法)。

Fork/Join框架的实现原理

  ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责将存放程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。

ForkJoinPool简单示例

package constant;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;

public class ForkJoinTask extends RecursiveTask<List<String>> {
	/**
	 * 
	 * @author: FangRen
	 * @version V2.0
	 * @date: 2018年7月2日 下午4:55:15
	 */
	private static final long serialVersionUID = 1L;
	
	private int THRESHOLD = 2;

	private int start;

	private int end;



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

	@Override
	protected List<String> compute() {
		 List<String> list = new ArrayList<>();
		//如果任务足够小就计算任务
		boolean canCompute = (end - start) <= THRESHOLD;
		if (canCompute) {
			for (int i = start; i < end; i++) {
				try {
					Thread.currentThread().sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				 System.out.println("线程池中线程数目:"+ForkJoinTask.getPool().getPoolSize()+",队列中等待执行的任务数目:"+
						 ForkJoinTask.getPool().getQueuedTaskCount());
				list.add("wo");
			}
			return list;
		}

		int middle = (end + start) / 2;
		ForkJoinTask task1 = new ForkJoinTask(start, middle);
		ForkJoinTask task2 = new ForkJoinTask(middle, end);
		invokeAll(task1, task2);
		//等待任务执行结束合并其结果
        List<String> leftResult = task1.join();
        List<String> rightResult = task2.join();
        list.addAll(rightResult);
        list.addAll(leftResult);
        
		return list;
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		 
		List<String> list = new ArrayList<String>();
        for(int i=1;i<=330;i++){
            list.add("i------"+i);
        }
        System.out.println(list.size());
        long s1 = System.currentTimeMillis();
        // 参数默认是cpu数(可以自定义)
		ForkJoinPool pool = new ForkJoinPool();
        // 提交可分解的PrintTask任务
		Future<List<String>> ss =  pool.submit(new ForkJoinTask(0,list.size()));
        //线程阻塞,等待所有任务完成
		pool.awaitTermination(1, TimeUnit.SECONDS);
        // 关闭线程池
        pool.shutdown();
        long s2 = System.currentTimeMillis();
        long s =s2-s1;
        System.out.println(s);
	}
}

猜你喜欢

转载自blog.csdn.net/qq_25224749/article/details/81146556