High Concurrency Exploration (13): Concurrent Container JUC -- Components FutureTask, ForkJoin, BlockingQueue

FutureTask

FutureTask is a class in JUC, a deletable asynchronous computation class. This class provides a basic implementation of the Future interface, using related methods to start and cancel calculations, query whether the calculation is complete, and retrieve the calculation results. The get method can only be used to retrieve the result when the computation is complete; the get method will block if the computation has not yet completed. Once the computation is complete, the computation cannot be restarted or canceled (unless the computation is called using the runAndReset method).

Runnable vs Callable

Usually to implement a thread, we will use the method of inheriting Thread or implementing the Runnable interface. The two methods have a common defect that the execution result cannot be obtained after the task is executed. Since Java 1.5, Callable and Future have been provided, and these two interfaces can be used to obtain task execution results.

  • Runnable interface: the code is very simple, there is only one method run
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
  • Callable generic interface: There are generic parameters, and a call method is provided, which can return the result of the incoming generic parameter type after execution .
public interface Callable<V> {
    V call() throws Exception;
}

Future interface

The Future interface provides a series of methods for controlling the thread to perform calculations, as follows:

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

Instructions:

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 Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(new MyCallable());//线程池提交任务
        log.info("do something in main");
        Thread.sleep(1000);
        String result = future.get();//获取不到一直阻塞
        log.info("result:{}", result);
    }
}

Running result: blocking effect
write picture description here

FutureTask

Future implements the RunnableFuture interface, and the RunnableFuture interface inherits the Runnable and Future interfaces, so it can be executed in the thread as a Runnable, and can be used as a callable to get the return value.

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

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

FutureTask supports two parameter types, Callable and Runnable. When using Runnable, one more return result type can be specified.

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
}

Instructions:

public class FutureTaskExample {

    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);
    }
}

operation result:
write picture description here

ForkJoin

ForkJoin is a framework for executing tasks in parallel provided by Java 7. It is a framework for dividing large tasks into several small tasks, and after the small tasks are completed, the results are aggregated into the results of large tasks. The work stealing algorithm is mainly used , and the work stealing algorithm refers to a thread stealing tasks from other queues to execute.
write picture description here
During the stealing process, two threads will access the same queue. In order to reduce the competition between the stealing task thread and the stolen task thread, we usually use a double-ended queue to implement the work stealing algorithm. The thread of the stolen task always takes the task from the head of the queue, and the thread that steals the task takes the task from the tail of the queue.

limitation:

1. Tasks can only use fork and join as synchronization mechanisms. If other synchronization mechanisms are used, when they are operating in synchronization, worker threads cannot perform other tasks. For example, if the task is put to sleep in the fork framework, the thread that executes this task during the sleep period will not execute other tasks.
2. The tasks we split should not perform IO operations, such as reading and writing data files.
3. Tasks cannot throw checked exceptions. They must be handled by the necessary code.

Framework core:

There are two classes at the core: ForkJoinPool | ForkJoinTask
ForkJoinPool: Responsible for the implementation, including work stealing algorithms, managing worker threads and providing information about the status of tasks and their execution.
ForkJoinTask: Provides a mechanism to perform fork and join in tasks.

Usage: (analog sum operation)

@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 blocking queue

Main application scenarios: producer-consumer model, which is thread-safe
write picture description here

Blocking situation:

1. When the queue is full, perform the enqueue operation
2. When the queue is empty, perform the dequeue operation

Four methods:

BlockingQueue provides four sets of methods for inserting, removing, and checking. Each method reacts differently when it cannot be executed immediately.
write picture description here

  • Throws Exceptions : Throws an exception if it cannot be executed immediately.
  • Special Value: Returns a special value if it cannot be executed immediately.
  • Blocks: Block if not executed immediately
  • Times Out: Blocks for a period of time if it cannot be executed immediately, and returns a value if it has not been executed after the set time

Implementation class:

  • ArrayBlockingQueue: It is a bounded blocking queue. The internal implementation is an array. The capacity size is specified during initialization. Once the specified size is specified, it cannot be changed. Elements are stored in FIFO mode.
  • DelayQueue: Blocks internal elements. The internal elements must implement the Delayed interface. The Delayed interface inherits the Comparable interface. The reason is that the internal elements of the DelayQueue need to be sorted. Generally, they are sorted by expiration time priority.
public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}

DalayQueue is implemented internally by PriorityQueue and 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>();
    ...
}
  • LinkedBlockingQueue: The size configuration is optional. If the size is specified during initialization, it is bounded. No bounds (maximum integer value) if not specified. The internal implementation is a linked list, which saves data in the form of FIFO.
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);//不指定大小,无边界采用默认值,最大整型值
}
  • PriorityBlockingQueue: Blocking queue with priority. Unbounded queue, allowing insertion of nulls. The inserted object must implement the Comparator interface, and the sorting rule of the queue priority is specified according to our implementation of the Comparable interface. We can get an iterator from PriorityBlockingQueue, but this iterator is not guaranteed to be iterated in priority order.
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;
}
  • SynchronusQueue: Only one element can be inserted, a synchronous queue, an unbounded non-cached queue, and no elements are stored.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325162225&siteId=291194637