本文是 《编写高质量代码之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);
}
}