一、Runnable
与Callable
1.Runnable
与Callable
对比
通常实现一个线程我们会使用继承Thread的方式或者实现Runnable接口,这两种方式有一个共同的缺陷就是在执行完任务之后无法获取执行结果。从Java1.5之后就提供了Callable与Future,这两个接口就可以实现获取任务执行结果。
Callable
接口`:public interface Callable<V> { V call() throws Exception; }
Runnable
接口public interface Runnable { public abstract void run(); }
- 都是接口;
- 都可以编写多线程程序;
- 都采用
Thread.start()
启动线程;
Runnable
没有返回值;Callable
可以返回执行结果,是个泛型,和Future
、FutureTask
配合可以用来获取异步执行的结果Callable
接口的call()
方法允许抛出异常;Runnable
的run()
方法异常只能在内部消化,不能往上继续抛
注:Callalbe
接口支持返回执行结果,需要调用FutureTask.get()
得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞;
二、Future
接口
1.Future
接口
Future
接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程序执行超时的关键。Future
接口是一个泛型接口,严格的格式应该是Future<V>
,其中V
代表了Future
执行的任务返回值的类型。
2.Future
接口的方法
boolean cancel (boolean mayInterruptIfRunning)
取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束boolean isCancelled ()
任务是否已经取消,任务正常完成前将其取消,则返回true
boolean isDone ()
任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true
V get () throws InterruptedException
,ExecutionException
等待任务执行结束,然后获得V
类型的结果。InterruptedException
线程被中断异常,ExecutionException
任务执行异常,如果任务被取消,还会抛出CancellationException
V get (long timeout, TimeUnit unit) throws InterruptedException, ExecutionException
,TimeoutException
同上面的get
功能一样,多了设置超时时间。参数timeout
指定超时时间,uint
指定时间的单位,在枚举类TimeUnit
中有相关的定义。如果计算超时,将抛出TimeoutException
3. Future
接口的使用
示例代码:
public class FutureExample1 {
/* 定义一个callable任务 */
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 Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
//利用future接收Callable的返回值
Future<String> future = executorService.submit(new MyCallable());
log.info("do something in main");
Thread.sleep(5000);
String result = future.get();
log.info("result:{}",result);
}
}
返回结果:
10:43:07.620 [main] INFO com.mmall.concurrency.example.aqs.FutureExample1 - do something in main
10:43:07.621 [pool-1-thread-1] INFO com.mmall.concurrency.example.aqs.FutureExample1 - do something in callable
10:43:12.625 [main] INFO com.mmall.concurrency.example.aqs.FutureExample1 - result:Done
Process finished with exit code 0
可以看到最后两条日志输出的时间,间隔了5秒钟,符合预期的效果。
三、FutureTask
1.FutureTask
介绍
FutureTask
是J.U.C
(java.util.concurrent
的简称)中的类,是一个可删除的异步计算类。这个类提供了Future
接口的的基本实现,使用相关方法启动和取消计算,查询计算是否完成,并检索计算结果。只有在计算完成时才能使用get
方法检索结果;如果计算尚未完成,get
方法将会阻塞。一旦计算完成,计算就不能重新启动或取消(除非使用runAndReset
方法调用计算)。
Future
实现了RunnableFuture
接口,而RunnableFuture
接口继承了Runnable
与Future
接口,所以它既可以作为Runnable
被线程中执行,又可以作为callable
获得返回值。
源码如下:
public class FutureTask<V> implements RunnableFuture<V> {
...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
2.FutureTask
的使用
示例代码:
@Slf4j
public class FutureTaskExample1 {
public static void main(String[] args) throws Exception {
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");
Thread.sleep(1000);
String result = futureTask.get();
log.info("result:{}",result);
}
}
执行结果:
10:52:34.076 [main] INFO com.mmall.concurrency.example.aqs.FutureTaskExample1 - do something in main
10:52:34.076 [Thread-0] INFO com.mmall.concurrency.example.aqs.FutureTaskExample1 - do something in callable
10:52:39.083 [main] INFO com.mmall.concurrency.example.aqs.FutureTaskExample1 - result:Done
Process finished with exit code 0
四、Fork/Join
框架
1.介绍
ForkJoin
是Java7
提供的一个并行执行任务的框架,是把大任务分割成若干个小任务,待小任务完成后将结果汇总成大任务结果的框架。主要采用的是工作窃取算法,工作窃取算法是指某个线程从其他队列里窃取任务来执行。- 在
Fork/Join
框架中,会将一个复杂任务分配到多个线程中执行,每个线程都对应一个队列,在队列中可能会有多个任务需要执行。当其中一个线程执行任务比较快,就会从其他线程中窃取一个任务放到自己的队列中去执行,充分利用线程进行并行计算; - 在窃取过程中两个线程会访问同一个队列,为了减少窃取任务线程和被窃取任务线程之间的竞争,通常我们会使用双端队列来实现工作窃取算法。被窃取任务的线程永远从队列的头部拿取任务,窃取任务的线程从队列尾部拿取任务。
2. 优缺点
**优点:**充分利用线程进行并行计算,并减小了线程间的竞争;
**缺点:**在某些情况下,依然存在竞争,如双端队列中只有一个队列时;
3.局限性
- 任务只能使用
fork
和join
作为同步机制,如果使用了其他同步机制,当他们在同步操作时,工作线程就不能执行其他任务了。比如在fork
框架使任务进入了睡眠,那么在睡眠期间内在执行这个任务的线程将不会执行其他任务了。 - 我们所拆分的任务不应该去执行
IO
操作,如读和写数据文件。 - 任务不能抛出检查异常。必须通过必要的代码来处理他们。
4.框架核心
核心有两个类:ForkJoinPool
| ForkJoinTask
ForkJoinPool
:负责来做实现,包括工作窃取算法、管理工作线程和提供关于任务的状态以及他们的执行信息。
ForkJoinTask
:提供在任务中执行fork和join的机制。
5.Fork/Join
框架使用示例
示例代码:
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
public static final int threshold = 2;//设定不大于两个数相加就直接for循环,不适用框架
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 rightResult = rightTask.join();
// 合并子任务
sum = leftResult + rightResult;
}
return sum;
}
public static void main(String[] args) {
ForkJoinPool forkjoinPool = new ForkJoinPool();
//生成一个计算任务,计算1+2+3+4...100
ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
//执行一个任务
Future<Integer> result = forkjoinPool.submit(task);
try {
log.info("result:{}", result.get());
} catch (Exception e) {
log.error("exception", e);
}
}
}
五、BlockingQueue
阻塞队列
1.应用场景
主要应用场景:生产者消费者模型,是线程安全的
2.导致阻塞的情况:
- 当队列满了进行入队操作;
- 当队列空了的时候进行出队列操作;
3.BlockingQueue
提供的四套方法
- Throws Exceptions :如果不能立即执行就抛出异常;
- Special Value:如果不能立即执行就返回一个特殊的值;
- Blocks:如果不能立即执行就阻塞;
- Times Out:如果不能立即执行就阻塞一段时间,如果过了设定时间还没有被执行,则返回一个值;
4.BlockingQueue
的实现类
ArrayBlockingQueue
:它是一个有界的阻塞队列,内部实现是数组,初始化时指定容量大小,一旦指定大小就不能再变。采用FIFO
方式存储元素。DelayQueue
:阻塞内部元素,内部元素必须实现Delayed
接口,Delayed
接口又继承了Comparable
接口,原因在于DelayQueue
内部元素需要排序,一般情况按过期时间优先级排序。LinkedBlockingQueue
:大小配置可选,如果初始化时指定了大小,那么它就是有边界的。不指定就无边界(最大整型值)。内部实现是链表,采用FIFO
形式保存数据。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);//不指定大小,无边界采用默认值,最大整型值
}
PriorityBlockingQueue
:带优先级的阻塞队列。无边界队列,允许插入null
。插入的对象必须实现Comparator
接口,队列优先级的排序规则就是按照我们对Comparable
接口的实现来指定的。我们可以从PriorityBlockingQueue
中获取一个迭代器,但这个迭代器并不保证能按照优先级的顺序进行迭代。SynchronusQueue
:只能插入一个元素,同步队列,无界非缓存队列,不存储元素。
Java并发编程学习系列
如有帮助,烦请点赞收藏一下啦 (◕ᴗ◕✿)