并行API

本文是 《编写高质量代码之Java》 秦小波 成林 著、《Java JDK 7学习笔记》林信良著 关于线程部分的 学习笔记,特别是《Java
JDK 7学习笔记》对于一些概念使用生活中的例子来解释,通俗易懂。

使用Executor

  • Runable用来定义可执行流程与可使用数据

  • Thread用来执行Runable。

  • JDK5开始定义了Executor接口,目的是将Runable的指定与实际如何执行分离。
    这里写图片描述

    因为Thread
    的建立与系统资源有关,如何建立Thread、是否重用Thread、何时销毁Thread、被指定的Runable何时排定给Thread执行,这些都是复制的议题。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class Pages {
    private URL []urls;
    private String []fileNames ;

    private Executor executor;

    public Pages(URL[] urls, String[] fileNames, Executor executor) {
        super();
        this.urls = urls;
        this.fileNames = fileNames;
        this.executor = executor;
    }

    public void download(){

        for (int i = 0; i < urls.length; i++) {
            final int index = i;

            executor.execute(new Runnable() {

                @Override
                public void run() {
                    try {
                        dump(urls[index].openStream(),new FileOutputStream(fileNames[index]));
                    } catch (FileNotFoundException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                }
            });
        }

    }


    protected void dump(InputStream openStream,
            FileOutputStream fileOutputStream) throws IOException {
        try(InputStream inputStream = openStream;OutputStream objectOutputStream =fileOutputStream ){
            byte[]data = new byte[1024];
            int lenght = -1;
            while( (lenght = inputStream.read(data))!= -1) {
                fileOutputStream.write(data, 0, lenght);
            }
        }

    }

}

可以定义一个DirectExecutor ,单纯调用传入execute()方法的Runable对象的run()方法

import java.util.concurrent.Executor;

public class DirectExecutor implements Executor{

    @Override
    public void execute(Runnable command) {
        command.run();
    }

}

这时主线程逐一执行指定的每个下载的任务,所有任务在同一个线程中执行

import java.net.MalformedURLException;
import java.net.URL;

public class DownloadExecutor {
    public static void main(String[] args) throws MalformedURLException {
        URL[] url = {new  URL("https://bookset.me/"),new  URL("https://bookset.me/")};

        String [] fileNames = {"5410.html","5808.html"};
        new Pages(url,fileNames,new DirectExecutor()).download();

    }
}

如果定义一个ThreadPerTaskExecutor

import java.util.concurrent.Executor;

public class ThreadPerTaskExecutor implements Executor {

    @Override
    public void execute(Runnable command) {
        new Thread(command).start();
    }

}

对于传入的Runable对象,都会建立一个Thread实例并start()执行

import java.net.MalformedURLException;
import java.net.URL;

public class DownloadExecutor {
    public static void main(String[] args) throws MalformedURLException {
        URL[] url = {new  URL("https://bookset.me/"),new  URL("https://bookset.me/")};

        String [] fileNames = {"5410.html","5808.html"};
//      new Pages(url,fileNames,new DirectExecutor()).download();
        //
        new Pages(url,fileNames,new ThreadPerTaskExecutor()).download();

    }
}

线程池

对于ThreadPerTaskExecutor的处理方法,当下载任务特别多时没下载一个页面都要新建一个线程,当下载结束时就丢弃该线程会过于浪费系统资源,这时候可以使用线程池来处理。

什么情况下使用线程池:线程数量多

有的时候需要建立一堆的线程来执行一些任务,然而频繁的建立线程有时会开销比较大。因为线程的建立必须和操作系统互动,如果能建立一个线程池(Thread pool)来管理这些小线程并加以重复使用,对于系统的效能是个改善的方式。

可以使用Executors来建立线程池,Executors有几个静态(static)方法,如下表所示:

方法说明
newCachedThreadPool()建立可以快速获取的线程,每个线程默认idle的时间为60秒
newFixedThreadPool包括固定数量的线程
newSingleThreadPool只有一个线程循环的执行指定给它的每个任务
newScheduledThreadPool可调度的线程
newSingleThreadScheduledExecutors单一可调度的线程

1.使用ThreadPoolExecutor

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DownloadExecutor {
    public static void main(String[] args) throws MalformedURLException {
        URL[] urls = {new  URL("https://bookset.me/"),new  URL("https://bookset.me/")};

        String [] fileNames = {"5410.html","5808.html"};
//      new Pages(url,fileNames,new DirectExecutor()).download();
        //
//      new Pages(url,fileNames,new ThreadPerTaskExecutor()).download();


        ExecutorService executorService = Executors.newCachedThreadPool();

        new Pages(urls, fileNames, executorService).download();

        executorService.shutdown();

    }
}
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureCallabledemo {
    public static long fibonacci(long n){
        if(n <= 1){
            return n;
        }else{
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }

    public static void main(String[] args) throws ExecutionException {
        FutureTask<Long> fib30 = new FutureTask<>(new Callable<Long>() {

            @Override
            public Long call() throws Exception {
                return fibonacci(30);
            }
        });

        System.out.println("求 30");

        new Thread(fib30).start();
        System.out.println("忙别的去了");
        try {
            Thread.sleep(5000);

            System.out.println(fib30.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

FutureTask和Callable

Java 1.5开始引入了一个新的接口Callable,它类似于Runable接口,可以传回结果对象。
这里写图片描述

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 
 * @author Administrator
 * 本文是 编写高质量代码之Java 秦小波 成林 著
 *建议124:异步运算考虑使用Callable接口  学习笔记
 *多线程应用有两种实现方式,一种是实现Runnable接口,另一种是继承Thread类,这两个方式都有缺点:
 *run方法没有返回值,不能抛出异常(这两个缺点归根到底是Runable接口的缺陷,
 *Thread也是实现了Runnable接口)
 *,如果需要知道一个线程的运行结果就需要用户自行设计,线程类自身也不能提供返回值和异常。
 *但是从Java 1.5开始引入了一个新的接口Callable,它类似于Runable接口,
 *实现它就可以实现多线程任务,Callable的接口定义如下:
 *public interface Callable<V> {
   **
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     *
     *具有返回值,并可抛出异常
    V call() throws Exception;
}
实现Callable接口的类,只是表明它是一个可调用的任务,并不表示它具有多线程运算能力,还是需要执行器来执行的。
,Executors是一个静态工具类,提供了异步执行器的创建能力,如单线程执行器newSingleThreadExecutor、固定线程数量的执行器newFixedThreadPool等,
一般它是异步计算的入口类。Future关注的是线程执行后的结果,比如有没有运行完毕,执行结果是多少等
此类异步计算的好处是
尽可能多地占用系统资源,提供快速运算。
可以监控线程执行的情况,比如是否执行完毕、是否有返回值、是否有异常等。
可以为用户提供更好的支持,比如例子中的运算进度等。
 */
public class UseCallable {
    public static void main(String[] args) throws Exception {
        //生成一个单线程的异步执行器
        ExecutorService es = Executors.newSingleThreadExecutor();
        //线程执行后的期望值
        Future<Integer>future =es.submit(new TexCalculator(100));
        while (!future.isDone()) {
            //还没有运算完成,等待200毫秒
            TimeUnit.MILLISECONDS.sleep(200);
            System.out.println("#");
        }
        System.out.println("\n计算完成,税金是:"+future.get()+"元");
        es.shutdown();
    }
}
/*
 * #
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

计算完成,税金是:10元

 */


/*
 * 税款计算器
 */

class  TexCalculator implements Callable<Integer>{
    //本金
    private int seedMoney;
    @Override
    public Integer call() throws Exception {
        //复杂计算,运行一次需要10秒
        TimeUnit.MILLISECONDS.sleep(10000);
        return seedMoney/10;
    }
    public TexCalculator(int seedMoney){
        this.seedMoney=seedMoney;
    }

}

FutureTask

FutureTask是一个代理,真正执行任务的是Callable对象,使用另一个线程启动FutureTask,之后就可以做其它事了,。

适用场景:比如用户可能需要快速翻页浏览文件,但在浏览到有图片的页数时,由于图片文件很大,导致图片加载很慢,造成用户浏览文件时会有停顿的感觉。因此希望打开文件的同时,仍有一个后台作业线程继续加载图片这样会有更好的用户体验。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureCallabledemo {
    public static long fibonacci(long n){
        if(n <= 1){
            return n;
        }else{
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
    }

    public static void main(String[] args) throws ExecutionException {
        FutureTask<Long> fib30 = new FutureTask<>(new Callable<Long>() {

            @Override
            public Long call() throws Exception {
                return fibonacci(30);
            }
        });

        System.out.println("求 30");
        //假设现在做的其它事情
        new Thread(fib30).start();
        System.out.println("忙别的去了");
        try {
            Thread.sleep(5000);

            System.out.println(fib30.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

ScheduledExecutorServicedemo

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServicedemo {

    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService  = Executors.newSingleThreadScheduledExecutor();

        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

            @Override
            public void run() {

                System.out.println(new java.util.Date());

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }               

            }
        }, 2000, 1000, TimeUnit.MICROSECONDS);
    }
}

使用ForkJoinPool

ForkJoin是JDK7 新添加的API ,主要目的是解决分而治之的问题。
所谓的分而治之:是指这些问题的解决,可以分解为性质相同的子问题,子问题还可以分解为更小的子问题,将相同的子问题解决并收集运算结果(如果必要的话),
整体的问题就解决了。
下边举3个例子

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

/***
 * ForkJoin是JDK7 新添加的API ,主要目的是解决分而治之的问题。
 * 所谓的分而治之:是指这些问题的解决,可以分解为性质相同的子问题,子问题还可以分解为更小的子问题,将相同的子问题解决并收集运算结果(如果必要的话),
 * 整体的问题就解决了。 
 * 
 * 如果需要返回结果可以使用:RecursiveTask
 * 
 * @author Administrator
 *
 */

//1.继承至RecursiveTask
public class Fibonacci extends RecursiveTask<Integer> {
    final int n;

    Fibonacci(int n) {
        this.n = n;
    }
    //2操作compute方法
    @Override
    protected Integer compute() {
        if (n <= 1)
            return n;
        //3 分解出 n -1 子任务
        Fibonacci f1 = new Fibonacci(n - 1);
        //4 请ForkJoinPool分配线程来执行这个子任务
        f1.fork();
        //5 分解出 n - 2子任务
        Fibonacci f2 = new Fibonacci(n - 2);
        //6 f2.compute() 直接执行这个子任务   ;7 f1.join()取得此子任务的执行结果
        return f2.compute() + f1.join();
    }

    public static void main(String[] args) {
        Fibonacci fibonacci =  new Fibonacci(23);
        ForkJoinPool mainPool = new ForkJoinPool();
        System.out.println(mainPool.invoke(fibonacci));

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

/**
 * 在实际中,fork/join框架发挥作用的场合是很多的,比如在一个目录包含的所有文本文件中搜索某个关键词时,可以为每个文件创建一个子任务。
 * 这种实现方式的性能要优于单线程的查找方式。如果相关的功能可以用递归和分治算法来解决,就适合使用fork/join框架。
 * 
 * @author wanghaha
 * 
 */
public class MaxValue {
    private static final int RANGE_LENGTH = 2000;
    private final ForkJoinPool forkJoinPool = new ForkJoinPool();

    @SuppressWarnings("unused")
    private static class MaxValueTask extends RecursiveTask<Long> {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
        private final long[] array;
        private final int start;
        private int end;

        @Override
        protected Long compute() {
            Long max = Long.MAX_VALUE;
            if (end - start <= RANGE_LENGTH) {
                for (int i = 0; i < end; i++) {
                    if (array[i] > max) {
                        max = array[i];
                    }
                }
            } else {
                int mid = (start + end) / 2;
                MaxValueTask lowTask = new MaxValueTask(array, start, mid);

                MaxValueTask hightTask = new MaxValueTask(array, start, mid);

                lowTask.fork();
                hightTask.fork();

                max = Math.max(max, lowTask.join());
                max = Math.max(max, hightTask.join());
            }
            return max;
        }

        public MaxValueTask(long[] array, int start, int end) {
            super();
            this.array = array;
            this.start = start;
            this.end = end;
        }

    };

    public void calculte(long[] array) {
        MaxValueTask task = new MaxValueTask(array, 0, array.length);
        Long result = forkJoinPool.invoke(task);
        System.out.println(result);

    }

    public static void main(String[] args) {
        long[] array = { 2, 22, 2, 2, 13213 };
        new MaxValue().calculte(array);
    }

}
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;


/**
 * 
 * @author Administrator
 *  如果分而治之的过程中,子任务不须返回值,可以继承RecursiveAction并操作compute()方法
 *  简单的文档搜索功能
 */

public class SubDir extends RecursiveAction{

    Path path;
    String pattern;

    public SubDir(Path path, String pattern) {
        super();
        this.path = path;
        this.pattern = pattern;
    }   
    @Override
    protected void compute() {
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)){
            List<SubDir> subDirs = new ArrayList<SubDir>();
            for (Path subPath : stream) {
                if(Files.isDirectory( subPath)){
                    subDirs.add(new SubDir(subPath, pattern));
                }else if(subPath.toString().endsWith(pattern)){
                    System.out.println(subPath.toString());
                }
            }
            ForkJoinTask.invokeAll(subDirs);
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    public static void main(String[] args) {
        String path = "C:\\Users\\Administrator";
        String fileName ="数学分析(套装全3册).azw3";
        ForkJoinPool mainJoinPool = new ForkJoinPool();
        SubDir subDir = new SubDir(Paths.get(path), fileName);
        mainJoinPool.invoke(subDir);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值