Java并发编程——J.U.C组件FutureTask、ForkJoin、BlockingQueue

一、RunnableCallable

1.RunnableCallable对比

通常实现一个线程我们会使用继承Thread的方式或者实现Runnable接口,这两种方式有一个共同的缺陷就是在执行完任务之后无法获取执行结果。从Java1.5之后就提供了Callable与Future,这两个接口就可以实现获取任务执行结果。

  • 源码区别:

  1. Callable接口`:
    public interface Callable<V> {
        V call() throws Exception;
    }
    
  2. Runnable接口
    public interface Runnable {
        public abstract void run();
    }
    
  • 相同点

  1. 都是接口;
  2. 都可以编写多线程程序;
  3. 都采用Thread.start()启动线程;
  • 不同点:

  1. Runnable没有返回值;Callable可以返回执行结果,是个泛型,和FutureFutureTask配合可以用来获取异步执行的结果
  2. Callable接口的call()方法允许抛出异常;Runnablerun()方法异常只能在内部消化,不能往上继续抛
注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞;

二、Future接口

1.Future接口

  • Future介绍:Future接口是Java线程Future模式的实现,可以来进行异步计算。

  1. Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程序执行超时的关键。
  2. 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介绍

FutureTaskJ.U.Cjava.util.concurrent的简称)中的类,是一个可删除的异步计算类。这个类提供了Future接口的的基本实现,使用相关方法启动和取消计算,查询计算是否完成,并检索计算结果。只有在计算完成时才能使用get方法检索结果;如果计算尚未完成,get方法将会阻塞。一旦计算完成,计算就不能重新启动或取消(除非使用runAndReset方法调用计算)。

Future实现了RunnableFuture接口,而RunnableFuture接口继承了RunnableFuture接口,所以它既可以作为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.介绍

  • ForkJoinJava7提供的一个并行执行任务的框架,是把大任务分割成若干个小任务,待小任务完成后将结果汇总成大任务结果的框架。主要采用的是工作窃取算法,工作窃取算法是指某个线程从其他队列里窃取任务来执行。
  • Fork/Join框架中,会将一个复杂任务分配到多个线程中执行,每个线程都对应一个队列,在队列中可能会有多个任务需要执行。当其中一个线程执行任务比较快,就会从其他线程中窃取一个任务放到自己的队列中去执行,充分利用线程进行并行计算;
  • 在窃取过程中两个线程会访问同一个队列,为了减少窃取任务线程和被窃取任务线程之间的竞争,通常我们会使用双端队列来实现工作窃取算法。被窃取任务的线程永远从队列的头部拿取任务,窃取任务的线程从队列尾部拿取任务。
    在这里插入图片描述

2. 优缺点

**优点:**充分利用线程进行并行计算,并减小了线程间的竞争;
**缺点:**在某些情况下,依然存在竞争,如双端队列中只有一个队列时;

3.局限性

  1. 任务只能使用forkjoin作为同步机制,如果使用了其他同步机制,当他们在同步操作时,工作线程就不能执行其他任务了。比如在fork框架使任务进入了睡眠,那么在睡眠期间内在执行这个任务的线程将不会执行其他任务了。
  2. 我们所拆分的任务不应该去执行IO操作,如读和写数据文件。
  3. 任务不能抛出检查异常。必须通过必要的代码来处理他们。

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.导致阻塞的情况:

  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并发编程学习系列

如有帮助,烦请点赞收藏一下啦 (◕ᴗ◕✿)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值