Multithreaded programming learning six (blocking queue in Java).

introduce

Blocking Queue means that when the queue is full, the queue blocks threads that insert elements until the queue is not satisfied; when the queue is empty, the queue blocks threads that acquire elements until the queue is not empty.A blocking queue is a container that producers use to store elements and consumers use to get them.

When a thread inserts/gets an action blocked by a full/empty queue, the queue also provides some mechanism to handle it, throw an exception, or return a special value, or the thread waits..

Method/Processing throw Return Special Values Always Blocked Timeout Exit
Insert Method add(e) offer(e) put(e) offer(e, timeout, unit)
Remove Method remove(o) poll() take() poll(timeout, unit)
Check Method element() peek() - Do not remove elements Not available Not available

tips: if the queue is unbounded, the put method will never be blocked; the offer method always returns true.

Blocking queues in Java:

ArrayBlockingQueue

ArrayBlockingQueue is a bounded blocking queue implemented as an array.This queue sorts elements according to the first-in-first-out (FIFO) principle and does not guarantee fair access to threads by default.

Condition s achieve blocking by controlling concurrency with the reentrant exclusive lock ReentrantLock.

public class ArrayBlockingQueueTest {

    /**
     * 1. Since there is a bounded blocking queue, the initial size needs to be set
     * 2. Fairness can be set by default, which does not guarantee fair access to blocked threads
     */
    private static ArrayBlockingQueue<String> QUEUE = new ArrayBlockingQueue<>(2, true);

    public static void main(String[] args) throws InterruptedException {

        Thread put = new Thread(() -> {
            // 3. Try inserting elements
            try {
                QUEUE.put("java");
                QUEUE.put("javaScript");
                // 4. Elements are full, blocking threads
                QUEUE.put("c++");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        put.start();
        Thread take = new Thread(() -> {
            try {
                // 5. Get an element
                System.out.println(QUEUE.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        take.start();
        // 6 javaScript,c++
        System.out.println(QUEUE.take());
        System.out.println(QUEUE.take());
    }
}

LinkedBlockingQueue

LinkedBlockingQueue is a bounded blocking queue implemented with a one-way chain table.The default and maximum length of this queue is Integer.MAX_VALUE.This queue sorts elements according to FIFO principle.

Like Array BlockingQueue, ReentrantLock controls concurrency, except that it uses two exclusive locks to control consumption and production. takeLock and putLock locks control production and consumption without interruption. As long as the queue is not full, the production line can always be produced; as long as the queue is not empty, consumptionThreads can be consumed all the time without blocking each other with exclusive locks.

tips: To avoid inaccurate concurrent calculations due to the use of double locks, an AtomicInteger variable is used to count the total number of elements.

LinkedBlockingDeque

LinkedBlockingDeque ue is a bounded blocking queue consisting of a two-way chain table structure that can insert and remove elements from both ends of the queue.It implements the BlockingDequeinterface, with many methods such as addFirst, addLast, offerFirst, offerLast, peekFirst, and peekLast, ending with the First word to insert, get, or remove the first element of a double-ended queue.A method that ends with the Last word to insert, get, or remove the last element of a double-ended queue.

LinkedBlockingDeque N ode implements a two-way queue by adding a variable prev to the previous Node.Similar to Array BlockingQueue in concurrency control, a single ReentrantLock is used to control concurrency.Because both ends of the queue can be consumed and produced, a shared lock is used.

A two-way blocking queue can be used in Work Stealing mode.

public class LinkedBlockingDequeTest {

    private static LinkedBlockingDeque<String> DEQUE = new LinkedBlockingDeque<>(2);

    public static void main(String[] args) {
        DEQUE.addFirst("java");
        DEQUE.addFirst("c++");
        // java
        System.out.println(DEQUE.peekLast());
        // java
        System.out.println(DEQUE.pollLast());
        DEQUE.addLast("php");
        // c++
        System.out.println(DEQUE.pollFirst());
    }
}

The tips:take() method calls takeFirst(), which should be used with caution.

PriorityBlockingQueue

PriorityBlockingQueue is an underlying unbounded blocking queue implemented by arrays with sorting capabilities.Because it is an unbound queue, the insert will never be blocked.By default, elements are in a natural ascending order.You can also customize the class implementation compareTo() method to specify the element collation, or specify the construction parameter Comparator to sort elements when you initialize PriorityBlockingQueue.

The bottom tier also uses ReentrantLock to control concurrency, and since only acquisition can block, only one Condition (only notification of consumption) is used.

public class PriorityBlockingQueueTest {

    private static PriorityBlockingQueue<String> QUEUE = new PriorityBlockingQueue<>();

    public static void main(String[] args) {
        QUEUE.add("java");
        QUEUE.add("javaScript");
        QUEUE.add("c++");
        QUEUE.add("python");
        QUEUE.add("php");
        Iterator<String> it = QUEUE.iterator();
        while (it.hasNext()) {
            // c++  javaScript  java  python  php
            // Same priority does not guarantee sorting order
            System.out.print(it.next() + "  ");
        }
    }
}

DelayQueue

DelayQueue is an unbounded blocking queue that supports delayed acquisition of elements.Queues are implemented using PriorityQueue.The elements in the queue must implement the Delayed interface. The elements are sorted by the delay priority, with the shorter delay being the first, and the elements can only be extracted from the queue at the end of the delay period.

Similar to PrirityBlockingQueue, the bottom level is an array, using a ReentrantLock to control concurrency.

Scenarios:

  1. The design of a caching system: DelayQueue can be used to save the validity period of cached elements, and a thread loop is used to query DelayQueue. Once an element is retrieved from DelayQueue, the validity period of the cache is reached.
  2. Timed Task Scheduling: Use DelayQueue to save the task and execution time that will be executed on that day, and start executing once the task is retrieved from DelayQueue, for example, TimerQueue is implemented using DelayQueue.
public class DelayElement implements Delayed, Runnable {

    private static final AtomicLong SEQUENCER = new AtomicLong();
    /**
     * Identification element order
     */
    private final long sequenceNumber;
    /**
     * Delay time, in nanoseconds
     */
    private long time;

    public DelayElement(long time) {
        this.time = System.nanoTime() + time;
        this.sequenceNumber = SEQUENCER.getAndIncrement();
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(time - System.nanoTime(), NANOSECONDS);
    }

    @Override
    public int compareTo(Delayed other) {
        // compare zero if same object
        if (other == this) {
            return 0;
        }
        if (other instanceof DelayElement) {
            DelayElement x = (DelayElement) other;
            long diff = time - x.time;
            if (diff < 0) {
                return -1;
            } else if (diff > 0) {
                return 1;
            } else if (sequenceNumber < x.sequenceNumber) {
                return -1;
            } else {
                return 1;
            }
        }
        long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
        return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
    }

    @Override
    public void run() {
        System.out.println("sequenceNumber" + sequenceNumber);
    }

    @Override
    public String toString() {
        return "DelayElement{" + "sequenceNumber=" + sequenceNumber + ", time=" + time + '}';
    }
}
public class DelayQueueTest {

    private static DelayQueue<DelayElement> QUEUE = new DelayQueue<>();

    public static void main(String[] args) {
        // 1. Add 10 parameters
        for (int i = 1; i < 10; i++) {
            // Random delay in 2.5 seconds
            int nextInt = new Random().nextInt(5);
            long convert = TimeUnit.NANOSECONDS.convert(nextInt, TimeUnit.SECONDS);
            QUEUE.offer(new DelayElement(convert));
        }
        // 3. Query Element Sorting - Short Delay Top
        Iterator<DelayElement> iterator = QUEUE.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        // 4. Element delay output is observed
        while (!QUEUE.isEmpty()) {
            Thread thread = new Thread(QUEUE.poll());
            thread.start();
        }
    }
}

LinkedTransferQueue

LinkedTransferQueue is an unbounded blocking TransferQueue queue composed of a chain table structure.

A large number of CAS operations are used in concurrency control, and no locks are used.

LinkedTransferQueue has more tryTransfer and transfer methods than other blocked queues.

  1. Transfers: Transfers the element to a consumer, waiting if necessary to do so. Stored elements must wait until there is consumer consumption before returning.
  2. tryTransfer: Transfers the element to a waiting consumer immediately, if possible. If a consumer is waiting for a consumer element, pass in the element to the consumer.Otherwise, return to false immediately, without waiting for consumption.

SynchronousQueue

SynchronousQueue is a blocked queue that does not store elements.Each put operation must wait for a take operation, otherwise continuing the put operation will be blocked.

SynchronousQueue, by default, uses an unfair policy for thread access to the queue, does not use locks, and is all concurrent through CAS operations. It has a very high throughput and is better than LinkedBlockingQueue and Array BlockingQueue and is ideal for handling some highly efficient transitivity scenarios.Executors.newCachedThreadPool() uses SynchronousQueue for task delivery.

public class SynchronousQueueTest {

    private static class SynchronousQueueProducer implements Runnable {

        private BlockingQueue<String> blockingQueue;

        private SynchronousQueueProducer(BlockingQueue<String> queue) {
            this.blockingQueue = queue;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    String data = UUID.randomUUID().toString();
                    System.out.println(Thread.currentThread().getName() + " Put: " + data);
                    blockingQueue.put(data);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private static class SynchronousQueueConsumer implements Runnable {

        private BlockingQueue<String> blockingQueue;

        private SynchronousQueueConsumer(BlockingQueue<String> queue) {
            this.blockingQueue = queue;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    System.out.println(Thread.currentThread().getName() + " take(): " + blockingQueue.take());
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {

        final BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
        SynchronousQueueProducer queueProducer = new SynchronousQueueProducer(synchronousQueue);
        new Thread(queueProducer, "producer - 1").start();
        SynchronousQueueConsumer queueConsumer1 = new SynchronousQueueConsumer(synchronousQueue);
        new Thread(queueConsumer1, "consumer — 1").start();
        SynchronousQueueConsumer queueConsumer2 = new SynchronousQueueConsumer(synchronousQueue);
        new Thread(queueConsumer2, "consumer — 2").start();
    }
}

 
 

  1. Reference Book: The Art of Java Concurrent Programming
  2. Reference Blog: https://www.cnblogs.com/konck/p/9473677.html

Tags: Java Javascript PHP Python

Posted on Tue, 03 Sep 2019 20:45:03 -0400 by shu