J.U.C组件拓展
1. Future
- Callable 与 Runnable 接口对比
- Future 接口
- FutureTask 类
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
/** * Created By liuyao on 2018/4/22 17:30. */
@Slf4j
public class FutureExample {
static class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
log.info("do something in callable");
Thread.sleep(5000);
return "Done";
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService= Executors.newCachedThreadPool();
Future<String> future=executorService.submit(new MyCallable());
log.info("do something in main");
String result=future.get();
log.info("result : {}",result);
}
}
执行结果是:
1.2 FutureTask
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/** * Created By liuyao on 2018/4/22 17:51. */
@Slf4j
public class FutureTaskExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask=new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
log.info("do something in callable");
Thread.sleep(5000);
return "Done";
}
});
new Thread(futureTask).start();
log.info("do something in main");
String result=futureTask.get();
log.info("result : {}",result);
}
}
利用FutureTask也可以得到相同的结果
1.3 Fork/Join 框架
JDK 7 提供的一个并行执行任务的框架,将大任务分割成多个小任务,最后将小任务汇总得到最后的结果。
工具窃取算法:
当一个线程的任务执行完后,去其他线程窃取任务执行,如下面的线程一执行完任务后,去线程二执行任务,线程二是从上往下执行,线程一从下往上窃取。故线程一般采用的是双端队列,被窃取的从头部拿任务执行,窃取的从尾部拿任务执行。
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
/** * Created By liuyao on 2018/3/25 17:08. */
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
private static final int THRESHOLD = 100; //阈值
private int start;
private int end;
public ForkJoinTaskExample(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;
ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
// 执行子任务
leftTask.fork();
rightTask.fork();
// 等待子任务执行完,得到结果
int leftResult = leftTask.join();
int rightRsult = rightTask.join();
// 合并子任务
sum = leftResult + rightRsult;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkJoinPool = new ForkJoinPool();
// 生成一个计算任务,负责计算1+...+100000
ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100000);
// 执行一个任务
Future<Integer> result = forkJoinPool.submit(task);
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
1.4 BlockingQueue
阻塞队列,存在两种阻塞情况,当一个线程从一个空的队列获取任务时和一个线程向一个满的队列放入任务时。主要用在生产者和消费者场景。
如果方法不能马上执行 | Throws Expection ,抛出异常 | Special Value 返回特殊值 | Blocks 阻塞 | Times Out 阻塞一定时间,最后返回特殊值 |
---|---|---|---|---|
Insert | add(o) | offer(o) | put(o) | offer(o,timeout,timeunit) |
Remove | remove(o) | poll() | take() | poll(time,timeunit) |
Examine | element() | peek() |
* ArrayBlockingQueue:内部实现是一个数组,有容量限制,初始化时要设置容量。FIFO操作数据。
DelayQueue:阻塞内部元素,必须实现Delay接口,该接口又继承了Compable接口,一般按照元素的过期时间的优先级进行排序,内部实现是PriorityQueue和Lock,在定时关闭连接等
LinkedBlockingQueue:大小配置可选,否者将是Int最大值,内部实现是链表,和ArrayBlockingQueue很像
PriorityBlockingQueue:有优先级的阻塞队列,无边界,有排序规则,允许插入null,插入的对象必须实现Compable接口。
-
SynchronousQueue:内部仅允许容乃一个元素,一个线程插入一个元素后阻塞,除非被其他线程消费。