[Concurrent programming - foundation] (g) JUC component extension

A, FutureTask get the return value of the thread

1.1, Callable and Runnable interface Comparison

Action (1) Callable interface and the interface Future

  • Usually implemented using a thread we will inherit Thread way or implement Runnable interface, these two methods have a common drawback is that after performing the task execution results can not be obtained. From then Java1.5 provides a Callable and Future, both interfaces can achieve access to the task execution results.

(2) Runnable Interface

  • The code is very simple, only one way to run
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

(3) Callable generic interface

  • There are generic parameters, provides a method call, the results of execution may return parameters passed in the generic type .
  • Both functions broadly similar, callable more powerful, it will be the return value of the thread execution, and can throw an exception
public interface Callable<V> {
    V call() throws Exception;
}

       1.2, Future interfaces

(1) Future interface provides a series of methods for computing control thread execution

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    //取消任务
    
    boolean isCancelled();
    //是否被取消
    
    boolean isDone();
    //计算是否完成
    
    V get() throws InterruptedException, ExecutionException;
    //获取计算结果,在执行过程中任务被阻塞
    
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
    //timeout等待时间、unit时间单位
}

(2) using the implement thread communication Callable and Future

@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 executor = Executors.newCachedThreadPool();
        Future<String> future = executor.submit(new MyCallable());
        log.info("do something in main");
        //如果上面的submit方法没有执行完,则一直阻塞在get这里
        String result = future.get();
        log.info("result :{}",result);
    }
}
/*输出:等待了五秒才获取到返回值
10:36:43.210 [main] INFO com.tangxz._8.FutureExample - do something in main
10:36:43.210 [pool-1-thread-1] INFO com.tangxz._8.FutureExample - do something in callable
10:36:48.213 [main] INFO com.tangxz._8.FutureExample - result :Done
*/

       1.3、FutureTask类

  • Parent class is RunnableFuture, RunnableFutureinherited Runnableand Futurethese two interfaces. This you can see FutureTaskalso perform similar Callabletypes of tasks.
  • If the constructor parameter is Runnablethen converted to its Callabletype. FutureTaskBut also as Runnablethe thread execution, it can be used as Futureget callablea return value.
  • Benefits: ① If there is a time-consuming need to calculate and return the logic value, while the value has not needed immediately, it can use this combination, with another to calculate a thread return value, while the current thread using this return value before, other operations can be done, when the return value until needed, and then obtained by future.

(1) Future realized RunnableFuture interface, and RunnableFuture interface extends Runnable and Future interface, so both can be used as Runnable thread of execution, but also as a callable get a return value.

public class FutureTask<V> implements RunnableFuture<V> {
    ...
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

(2) FutureTask supports two types of parameters, Callable and Runnable, when using Runnable, can specify a return multiple result type.

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

(3) FutureTask

@Slf4j
public class FutureTaskExample {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask<String> futureTask = new FutureTask<>(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);
    }
}

Two, Fork / Join framework

2.1, ForkJoin Profile

ForkJoin Java7 framework provides a parallel execution of the task, the task is to split into several small big task to be little larger than the aggregated frame to complete the task results of the task will result. The main uses a work-stealing algorithm , work-stealing algorithm means a thread queue stolen from other tasks to perform.

2.2, work-stealing algorithm

Here Insert Picture Description

  • Started from the top of the right, began to steal from the tail.
  • In order to reduce competition thread, the big task into smaller tasks, and assigned to different queues and queue threads correspondence.
  • 先执行完自己的任务,再帮助别人完成别人的任务,就从其它线中窃取任务来执行,在这时他们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,外面会使用双端队列,被窃取任务的队列永远从双端队列的头部拿任务来执行,而窃取任务的线程则永远从双端队列的尾部拿任务来执行。
  • 工作窃取算法的优点是充分利用线程进行并行计算并减少了线程间的竞争。
  • 工作窃取算法的缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时,同时这样也消耗了更多的系统资源,比如创建了多个线程和多个双端队列。
  • 对于fork框架而言,当一个任务正在等待他使用down操作创建的子任务结束时,执行这个任务的工作线程,查找其它未被执行的工作任务,并开始它的执行,通过这种方式线程充分的运用他们的运行时间来提高应用程序的性能。要实现这个目标,fork框架有一定的局限性。

2.3、ForkJoin局限性

  • 任务只能使用fork和join操作来作为同步机制,如果使用了其它同步机制,那他们在同步操作时,工作线程就不能执行其它任务了,比如在fork框架中使任务进入了睡眠,那么这个睡眠区间内,正在执行这个任务的工作线程就不会执行其它任务了。
  • 我们在执行的任务不应该去执行io操作,如读写数据文件
  • 任务不能抛出检查异常,必须通过必要的代码来处理他们

2.4、ForkJoin框架核心

  • 核心有两个类:ForkJoinPool | ForkJoinTask

  • (1)ForkJoinPool

    负责来做实现,包括工作窃取算法、管理工作线程和提供关于任务的状态以及他们的执行信息。

  • (2)ForkJoinTask

    提供在任务中执行fork和join的机制。

2.5、ForkJoin使用(模拟加和运算)

/**
 * @Info: 继承的类字面意思就是递归的意思
 * @Author: 唐小尊
 * @Date: 2019/12/29  9:41
 */
@Slf4j
public class ForkJoinTaskExample extends RecursiveTask<Integer> {
    private static final int threshold = 2;
    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
        ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
        //执行一个任务
        Future<Integer> result = forkJoinPool.submit(task);
        try {
            log.info("result:{}", result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

三、BlockingQueue 阻塞队列

Here Insert Picture Description

3.1、阻塞队列

        3.1.1、阻塞队列是什么
  • 阻塞队列是一个队列,在某些情况下对阻塞队列的访问可能会造成阻塞。
  • 阻塞队列是线程安全的,主要用在生产者消费者场景。
        3.1.2、被阻塞的情况
  • 当队列满了的时候进行入队列操作
  • 当队列为空时进行出队列操作

3.2、阻塞队列的四套方法

       BlockingQueue提供了四套方法,分别来进行插入、移除、检查。每套方法在不能立刻执行时都有不同的反应。
Here Insert Picture Description

  • Throws Exceptions :如果不能立即执行就抛出异常。
  • Special Value:如果不能立即执行就返回一个特殊的值。
  • Blocks:如果不能立即执行就阻塞
  • Times Out:如果不能立即执行就阻塞一段时间,如果过了设定时间还没有被执行,则返回一个值

3.3、BlockingQueue的实现类

       3.3.1、ArrayBlockingQueue
  • 有界的阻塞队列,内部实现是数组,容量有限,在其初始化的时候指定容量大小,指定之后不能再变,先进先出。采用FIFO方式存储数据。
       3.3.2、DelayQueue
  • 阻塞的是内部元素,DelayQueue里面的元素都必须实现一个接口,是juc里面的一个delay的接口,这个接口继承了conterable接口,因为DelayQueue里面的元素需要排序,一般都是按照元素过期的优先级进行排序。
public interface Delayed extends Comparable<Delayed> {
	long getDelay(TimeUnit unit);
}
  • 应用场景很多,比如:定时关闭连接、缓存对象、超时处理等多种场景。
  • 内部实现是PriorityQueue和ReentrantLock,就是锁和排序。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>
    implements BlockingQueue<E> {

    private final transient ReentrantLock lock = new ReentrantLock();
    private final PriorityQueue<E> q = new PriorityQueue<E>();
    ...
}
       3.3.3、LinkedBlockingQueue
  • 大小配置可选,如果初始化时指定了一个大小,那么他就是有边界的,不指定就是无边界的,使用了默认的最大的整型值,内部使用的链表,先进先出,最先插入的在尾部,移出的在头部
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);//不指定大小,无边界采用默认值,最大整型值
}
       3.3.4、PriorityBlockingQueue
  • 有优先级的阻塞队列。
  • 没有边界的队列,有排序规则,允许插入空对象。
  • 所有插入的对象必须实现conterable接口,队列优先级的排序规则就是按照我们对Comparable接口的实现来指定的。
  • 我们可以从PriorityBlockingQueue中获取一个迭代器,但这个迭代器并不保证能按照优先级的顺序进行迭代。
public boolean add(E e) {//添加方法
    return offer(e);
}
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    int n, cap;
    Object[] array;
    while ((n = size) >= (cap = (array = queue).length))
        tryGrow(array, cap);
    try {
        Comparator<? super E> cmp = comparator;//必须实现Comparator接口
        if (cmp == null)
            siftUpComparable(n, e, array);
        else
            siftUpUsingComparator(n, e, array, cmp);
        size = n + 1;
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
    return true;
}
       3.3.5、 SynchronousQueue
  • Internal allows only accommodate one element, when a thread insert an element, will be blocked, unless this element is consumed by another thread, so called him synchronization queue, he is an unbounded buffer queue.
Published 20 original articles · won praise 1 · views 560

Guess you like

Origin blog.csdn.net/weixin_42295814/article/details/103791878