Java核心技术 卷1 多线程----执行器(5)

  

    构建一个新的线程是有一定代码的,因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池(thread pool)。一个线程池中包含许多准备运行的空闲线程。将Runnable是交给线程池,就会有一个线程调用
run方法。当run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。    另一个使用线程池的理由是减少并发线程的数目。创建大量线程会大大降低性能甚至使虚拟机崩溃。如果有一个会创建许多线程的算法,应该使用一个线程数“固定的”线程池以限制并发线程的总数。
   执行器(Executor)类有许多静态工厂方法用来构建线程池。如下:

     方  法                                                                描 述
newCachedThreadPool            必要时创建新线程:空闲线程会被保留60秒
newFixedThreadPool                    该池包含固定数量的线程;空闲线程会一直保留
newSingleThreadExecutor               只有一个线程的“池”,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程)
newScheduledThreadPool                用于预定执行而构建的固定线程池,替代java.util.Timer
newSingleThreadScheduledExecutor      用于预定执行而构建的单线程“池”

  线程池

  newCachedThreadPool方法构建一个线程池,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。newFixedThreadPool方法构建一个具有固定大小的线程池。如果提交的任务数多于空闲的线程数,那么把得不到服务的。任务旋转到队列中。当其他任务完成以后再运行它们。newSingleThreadExecutor是一个退化了的大小为1的线程池:由一个线程执行提交的任务,一个接一个。这3个方法返回实现了ExecutorService接口的ThreadPoolExecutor类的对象。

  可用下面的方法之一将一个Runnable对象或Callable对象提交给ExecutorService:

java.util.concurrent.ExecutorService
Future<?> submit(Runnable task)               返回Future<?> 可以调用isDone、cancel或isCancelled。get方法在完成的时候只是简单的返回null
Future<T> submit(Runnable task,T result)
提交一个Runnable对象,并且Futrue的get方法在完成的时候返回指定的result对象。
Future<T> submit(Callable<T> task) 提交一个Callable,并且返回的Futrue对象将在计算结果准备好的时候的得到它。

 

  该线程池会在方便的时候尽早执行提交的任务。调用submit时,会得到一个Future对象,可用来查询该任务的状态。当用完一个线程池的时候,调用shutdown。该方法启动该池的关闭序列。被关闭的执行器不再接受新的任务。当所有的任务都完成以后,线程池中的线程死亡。另一种方法是调用shutdownNow。该池取消尚未开始的所有任务并试图中断正在运行的线程。

  总结使用连接池时应该做的事:

  1)调用Executors类中静态的方法newCachedThreadPool或newFixedThreadPool。

  2)调用submit提交Runnable或Callable对象。

  3)如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象。

  4)当不再提交任何任务时,调用shutdown。

  例如,前几篇笔记里的程序例子产生了大量的生命期很短的线程,每个目录产生一个线程。 以下代码将演示使用一个线程池来运行任务。(出于信息方面的考虑,这个程序打印出执行中池中最大的线程数。但是不能通过ExecutorService这个接口得到这一信息。因此必须将该pool对象强制转换为ThreadPoolExecutor类对象)。

package test.ThreadPool;

import java.io.File;
import java.util.Scanner;
import java.util.concurrent.*;

/**
 * Created by Administrator on 2017/11/27.
 */
public class ThreadPoolTest {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        System.out.print("输入目前路径:");
        String directory=in.nextLine();
        System.out.print("输入关键字:");
        String keyword=in.nextLine();
//1.调用Executors类中静态的方法newCachedThreadPool ExecutorService pool
= Executors.newCachedThreadPool(); MatchCounterThreadPool counter=new MatchCounterThreadPool(new File(directory),keyword,pool);
//2.调用submit提交Runnable或Callable对象。
     //3.如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象。 Future
<Integer> result=pool.submit(counter); try{ System.out.println(result.get()+" 匹配文件"); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }
//4.当不再提交任何任务时,调用shutdown。 pool.shutdown();
//返回线程池在该执行器生命周期中的最大尺寸
int largestPoolSize=((ThreadPoolExecutor)pool).getLargestPoolSize(); System.out.println("线程池大小="+largestPoolSize); } }
package test.ThreadPool;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 *  此任务对包含给定关键字的目录及其子目录中的文件进行计数
 */
public class MatchCounterThreadPool implements Callable<Integer> {
    private File directory;
    private String keyword;
    private ExecutorService pool;
    private int count;

    /**
     *
     * @param directory  开始查询的文件
     * @param keyword  寻找关键字
     * @param pool  用于提交子任务的线程池
     */
    public MatchCounterThreadPool(File directory, String keyword, ExecutorService pool) {
        this.directory=directory;
        this.keyword=keyword;
        this.pool=pool;
    }

    @Override
    public Integer call() throws Exception {
        count=0;
        try{
            File[] files=directory.listFiles();
            List<Future<Integer>> results=new ArrayList<>();
            for (File file:files
                    ) {
                if (file.isDirectory()){
                    MatchCounterThreadPool counter=new MatchCounterThreadPool(file,keyword,pool);
                    Future<Integer> result=pool.submit(counter);
                    results.add(result);
                }
                else {
                    if(search(file)){
                        count++;
                    }
                }
                for (Future<Integer> result:results
                        ) {
                    try{
                        count+=result.get();
                    }
                    catch (ExecutionException e){
                        e.printStackTrace();
                    }
                }
            }
        }catch (InterruptedException e){

        }

        return count;
    }
    /**
     * 搜索一个给定关键字的文件
     *
     * @param file
     * @return
     */
    public boolean search(File file) {
        try {
            try (Scanner in = new Scanner(file, "gbk")) {
                boolean isFound = false;
                while (!isFound && in.hasNextLine()) {
                    String line = in.nextLine();
                    if (line.contains(keyword)) {
                        isFound = true;
                    }
                }
                return isFound;
            }
        } catch (IOException e) {
            return false;
        }
    }
}

 

java.util.concurrent.Executors

 

  •  ExecutorService newCachedThreadPool()

  返回一个带缓存的线程池,该池在必要的时候创建线程,在线程空闲的60秒后终止线程。

  • ExcuorService newFixedThreadPool(int threads)

  返回一个线程池,该池中的线程数由参数指定。

  • ExecutorService newSingleThreadExecutor()

  返回一个执行器,它在一个单个的线程中依次执行各个任务。

java.util.concurrent.ThreadPoolExecutor
  •  int getLargestPoolSize()  返回线程池在该执行器生命周期中的最大尺寸。

 预定执行

  ScheduledExecutorService接口具有为预定执行(Scheduled Execution)或重复执行任务而设计的方法。它是一种允许使用线程池机制的java.util.Timer的泛化。Executors类的newScheduledThreadPool和newSingleThreadScheduledExecutor方法将返回实现了ScheduledExecutorService接口对象。

  可以预定Runnable或Callable在初始的延迟之后只运行一次。也可以预定一个Runnable对象周期性地运行。

java.util.concurrent.Executors
  •  ScheduledExecutorService newSheduledThreadPool(int threads) 返回一个线程池,它使用给定的线程数来调度任务。
  •  ScheduledExecutorService newSingleThreadScheduledExecutor() 返回一个执行器,它在一个单独的线程中调度任务。

 

java.util.concurrent.ScheduledExecutorService
  • ScheduledFuture<V> schedule(Callable<V> task,long time,TimeUnit unit)
  • ScheduledFuture<?> schedule(Runnable<?> task,long time,TimeUnit unit) 预订在指定的时间之后执行任务。
  • ScheduledFuture<V> scheduleAtFixedRate(Runnable task,long initialDelay,long period,TimeUnit unit) 预定在初始的延迟结束后,周期性地运行给定的任务,周期长度是period。
  • ScheduledFuture<V> scheduleWithFixedDelay(Runnable task,long initialDelay,long period,TimeUnit unit) 预定在初始的延迟结束后周期性地给定的任务,在一次调用完成和下一次调用开始之间有长度为delay的延迟。

 Fork-Join框架

  一个Web服务器可能会为每个连接分别使用一个线程。另外一些应用可能对每个处理器内核分别使用一个线程,来完成计算密集型任务,如图像或视频处理。java SE 7中引入了fork-join框架,专门用来支持后一类应用。

  在后台,fork-join框架使用了一种有效的智能方法来平衡可用线程的工作负载,这种方法称为工作密取(work stealing)。每个工作线程都有一个双端队列(deque)来完成任务。一个工作线程将子任务压入其双端队列的队头。(只有一个线程可以访问队头,所以不需要加锁)一个工作线程空闲时,它会从另一个双端队列的队尾"密取”一个任务。由于大的子任务都在队尾,这种密取很少出现。 

 

package test.ForkJoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

public class CountTask extends RecursiveTask<Integer> {
    public static final  int threshold=2;
    private int start;
    private int end;

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


    @Override
    protected Integer compute() {
        int sum=0;
        //如果任务足够小就计算任务
        boolean canCompute=(end-start)<=threshold;
        if (canCompute){
            for (int i = start; i <=end ; i++) {
                sum+=i;
            }
        }
        else {
            //如果任务大于阈值,就分裂成两个子任务计算
            int middle=(start+end)/2;
            CountTask leftTask=new CountTask(start,middle);
            CountTask rightTask=new CountTask(middle+1,end);
            //执行子任务
            leftTask.fork();
            rightTask.fork();

            //等待任务执行结束合并其结果
            int leftResult=leftTask.join();
            int rightResult=rightTask.join();
            //合并子任务
            sum=leftResult+rightResult;
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool forkJoinPool=new ForkJoinPool();
        //生成一个计算任务,计算1+2+3+4
        CountTask task=new CountTask(1,100);
        //执行一个任务
        Future<Integer> result=forkJoinPool.submit(task);

        System.out.println(result.get());
    }
}

posted on 2017-11-27 16:05  罗伯特☆狗剩  阅读(388)  评论(0编辑  收藏  举报

导航