Java.util.concurrent and package delivery guidelines

The preface

This article provides a brief introduction to the related development tools under the java.util.Concurrent package, guides you through the classes under the package and tries to use it in your projects.

This article will not explain the core issue of Java concurrency – the principles behind it, that is, if you are interested in those things, please refer to the Java Concurrency Guide.

When you find classes or interfaces that are missing, be patient. They will be added when the author is free.

directory

[TOC]

1. Block queue BlockingQueue

The BlockingQueue interface in the java.util.Concurrent package represents a queue where a thread puts and extracts instances. In this section I’ll show you how to use this BlockingQueue.

1.1 BlockingQueue usage

BlockingQueue is usually used when one thread produces objects and another thread consumes them. Here is an illustration of this principle:

One thread puts a BlockingQueue into it and another thread takes a BlockingQueue from it.

A thread will keep producing new objects and inserting them into the queue until the queue reaches its capacity. In other words, it is finite. If the blocking queue reaches its critical point, the production thread will block when inserting new objects into it. It blocks until the consuming thread removes an object from the queue.

The consuming thread will always pull objects from the blocking queue. If a consuming thread tries to fetch an object from an empty queue, the consuming thread will block until a producing thread drops an object into the queue.

1.2 BlockingQueue method

BlockingQueue has four different sets of methods for inserting, removing, and checking elements in the queue. Each method behaves differently if the requested action cannot be executed immediately. These methods are as follows:

operation Throw exceptions A specific value blocking timeout
insert add(o) offer(o) put(o) offer(o, timeout, timeunit)
remove remove(o) poll(o) take(o) poll(timeout, timeunit)
check element(o) peek(o) Do not use Do not use

Four different sets of behavioral explanations:

  • Throw exception: Throw an exception if the attempted action cannot be performed immediately.
  • Specific value: Returns a specific value (usually true/false) if the attempted operation cannot be performed immediately.
  • Block: If the attempted action cannot be executed immediately, the method call blocks until it can be executed.
  • Timeout: If the attempted operation cannot be executed immediately, the method call blocks until it can be executed, but the wait time does not exceed the given value. Returns a specific value to tell whether the operation was successful (typically true/false).

Unable to insert NULL into a BlockingQueue. If you try to insert null, BlockingQueue will throw a NullPointerException. You can access all elements in the BlockingQueue, not just the beginning and end elements. Let’s say you put an object in a queue for processing, but your application wants to cancel it. You can then call a method such as remove(o) to remove a particular object from the queue. But it’s not very efficient to do this, so you should try not to use this type of method unless you absolutely have to.

1.3 BlockingQueue implementation

BlockingQueue is an interface and you need to use one of its implementations to use BlockingQueue. Java.util.concurrent has the following BlockingQueue interface implementation (Java 6) :

  • ArrayBlockingQueue
  • DelayQueue
  • LinkedBlockingQueue
  • PriorityBlockingQueue
  • SynchronousQueue

1.4 Examples of using BlockingQueue in Java

Here is an example of using BlockingQueue in Java. This example uses the ArrayBlockingQueue implementation of the BlockingQueue interface.

First, the BlockingQueueExample class starts a Producer and a Consumer in two separate threads. The Producer injects strings into a shared BlockingQueue, and the Consumer pulls them out.

public class BlockingQueueExample {  

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

        BlockingQueue queue = new ArrayBlockingQueue(1024);  

        Producer producer = new Producer(queue);  
        Consumer consumer = new Consumer(queue);  

        new Thread(producer).start();  
        new Thread(consumer).start();  

        Thread.sleep(4000); }}123456789101112131415
Copy the code

Here is the Producer class. Notice how it sleeps for a second on every put() call. This will cause the Consumer to block while waiting for objects in the queue.

public class Producer implements Runnable{  

    protected BlockingQueue queue = null;  

    public Producer(BlockingQueue queue) {  
        this.queue = queue;  
    }  

    public void run(a) {  
        try {  
            queue.put("1");  
            Thread.sleep(1000);  
            queue.put("2");  
            Thread.sleep(1000);  
            queue.put("3");  
        } catch(InterruptedException e) { e.printStackTrace(); }}}1234567891011121314151617181920
Copy the code

Here is the Consumer class. It simply extracts objects from the queue and prints them to System.out

public class Consumer implements Runnable{  

    protected BlockingQueue queue = null;  

    public Consumer(BlockingQueue queue) {  
        this.queue = queue;  
    }  

    public void run(a) {  
        try {  
            System.out.println(queue.take());  
            System.out.println(queue.take());  
            System.out.println(queue.take());  
        } catch(InterruptedException e) { e.printStackTrace(); }}}123456789101112131415161718
Copy the code

Array block queue ArrayBlockingQueue

The ArrayBlockingQueue class implements the BlockingQueue interface.

ArrayBlockingQueue is a bounded blocking queue whose internal implementation puts objects into an array. Bounded means that it can’t store an infinite number of elements. It has an upper limit on the number of elements it can store at any one time. You can set this limit when you initialize it, but then you can’t change it.

ArrayBlockingQueue stores elements internally in FIFO(first-in, first-out) order. The first element in the queue is the oldest of all elements, and the last element is the shortest.

Here is an example of how to initialize ArrayBlockingQueue when using it:

BlockingQueue queue = new ArrayBlockingQueue(1024);  
queue.put("1");  
Object object = queue.take();  123
Copy the code

Here is an example BlockingQueue that uses Java generics. Notice how String elements are placed and extracted:

BlockingQueue<String> queue = new ArrayBlockingQueue<String>(1024);  
queue.put("1");  
String string = queue.take();  123
Copy the code

3. DelayQueue DelayQueue

DelayQueue implements the BlockingQueue interface.

DelayQueue holds elements until a specific delay expires. Injection of these elements must implement Java. Util. Concurrent. Of interface, the interface definition:

public interface Delayed extends Comparable<Delayed> {  

 public long getDelay(TimeUnit timeUnit);  

}  12345
Copy the code

DelayQueue will release each element after the value returned by the getDelay() method. If 0 or a negative value is returned, the delay is considered expired and the element will be released when the next take of the DelayQueue is called.

The getDelay instance passed to the getDelay method is an enumerated type that indicates the time period to be delayed. The TimeUnit enumeration will take the following values:

  • DAYS
  • HOURS
  • MINUTES
  • SECONDS
  • MILLISECONDS
  • MICROSECONDS
  • NANOSECONDS

As you can see, the Delayed interface also inherits the java.lang.Comparable interface, which means that Delayed objects can be compared with each other. This might be useful when sorting the elements in the DelayQueue so that they can be released in order by expiration time.

Here is an example of using DelayQueue:

public class DelayQueueExample {  

    public static void main(String[] args) {  
        DelayQueue queue = new DelayQueue();  
        Delayed element1 = newDelayedElement(); queue.put(element1); Delayed element2 = queue.take(); }}123456789
Copy the code

DelayedElement is an implementation class of the DelayedElement interface that I created that is not in the java.util.Concurrent package. You’ll need to create your own implementation of the Delayed interface to use the DelayQueue class.

4. LinkedBlockingQueue

The LinkedBlockingQueue class implements the BlockingQueue interface.

LinkedBlockingQueue stores its elements internally in a chained structure (linked nodes). The chain structure can select an upper limit if desired. If no upper limit is defined, integer.max_value is used as the upper limit.

LinkedBlockingQueue internally stores elements in FIFO(first in, first out) order. The first element in the queue is the oldest of all elements, and the last element is the shortest.

Here is sample code for initializing and using LinkedBlockingQueue:

BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();  
BlockingQueue<String> bounded   = new LinkedBlockingQueue<String>(1024);  
bounded.put("Value");  
String value = bounded.take();  1234
Copy the code

5. PriorityBlockingQueue with priority

PriorityBlockingQueue implements the BlockingQueue interface.

PriorityBlockingQueue is an unbounded concurrent queue. It uses the same sort rules as the java.util.PriorityQueue class. You cannot insert null values into this queue.

All elements inserted into PriorityBlockingQueue must implement the java.lang.Comparable interface. The order of the elements in that queue is therefore up to your own Comparable implementation.

Note that PriorityBlockingQueue does not enforce any specific behavior for elements with equal priority (compare() == 0). Also note that if you get an Iterator from a PriorityBlockingQueue, the Iterator does not guarantee that it will traverse the elements in priority order.

Here is an example of using PriorityBlockingQueue:

BlockingQueue queue   = new PriorityBlockingQueue();  
//String implements java.lang.Comparable  
queue.put("Value");  
String value = queue.take();  1234
Copy the code

6. Synchronize the SynchronousQueue

The SynchronousQueue class implements the BlockingQueue interface.

SynchronousQueue is a special queue that can hold only one element internally at a time. If the queue already has an element, a thread trying to insert a new element into the queue will block until another thread removes the element from the queue. Similarly, if the queue is empty, the thread trying to extract an element from the queue will block until another thread inserts a new element into the queue.

Therefore, calling this class a queue is obviously an exaggeration. It’s more like a meeting point.

7. Block the two-ended queue BlockingDeque

The BlockingDeque interface in the java.util.Concurrent package represents a two-ended queue where a thread places and extracts instances. In this section I will show you how to use BlockingDeque.

The BlockingDeque class is a two-ended queue that blocks the thread attempting to insert an element if it cannot; When an element cannot be extracted, it blocks the thread attempting to extract it.

A deque is short for “Double Ended Queue”. So, a double-endian queue is a queue where you can insert or extract elements from either end.

7.1 Use of BlockingDeque

BlockingDeque can be used when a thread is both the producer and consumer of a queue. BlockingDeque can also be used if the producer thread needs to insert data at both ends of the queue and the consumer thread needs to remove data at both ends of the queue. BlockingDeque illustration:

One BlockingDeque – threads can insert and extract elements on either side of a two-ended queue.

A thread produces elements and inserts them to either end of the queue. If the two-end queue is full, the insertion thread will block until a removal thread removes an element from the queue. If the two-end queue is empty, the removal thread will block until an insertion thread inserts a new element into the queue.

7.2 BlockingDeque method

BlockingDeque has four different sets of methods for inserting, removing, and checking elements in a two-ended queue. Each method behaves differently if the requested action cannot be executed immediately. These methods are as follows:

operation Throw exceptions A specific value blocking timeout
insert addFirst(o) offerFirst(o) putFirst(o) offerFirst(o, timeout, timeunit)
remove removeFirst(o) pollFirst(o) takeFirst(o) pollFirst(timeout, timeunit)
check getFirst(o) peekFirst(o) There is no There is no
operation Throw exceptions A specific value blocking timeout
insert addLast(o) offerLast(o) putLast(o) offerLast(o, timeout, timeunit)
remove removeLast(o) pollLast(o) takeLast(o) pollLast(timeout, timeunit)
check getLast(o) peekLast(o) There is no There is no

Four different sets of behavioral explanations:

  • Throw exception: Throw an exception if the attempted action cannot be performed immediately.
  • Specific value: Returns a specific value (usually true/false) if the attempted action cannot be performed immediately.
  • Block: If the attempted action cannot be executed immediately, the method call blocks until it can be executed.
  • Timeout: If the attempted action cannot be executed immediately, the method call blocks until it can be executed, but the wait time does not exceed the given value. Returns a specific value to tell whether the operation was successful (typically true/false).

7.3 BlockingDeque inherits from BlockingQueue

The BlockingDeque interface inherits from the BlockingQueue interface. This means that you can use BlockingDeque just like you use a BlockingQueue. If you do this, various insert methods add new elements to the end of the double-ended queue, and remove methods remove the elements at the beginning of the double-ended queue. As with the Insert and remove methods of the BlockingQueue interface.

Here is a concrete internal implementation of BlockingDeque’s methods on the BlockingQueue interface:

BlockingQueue BlockingDeque
add() addLast()
offer() offerLast()
put() putLast()
offer(e, time, unit) offerLast(e, time, unit)
remove() removeFirst()
poll() pollFirst()
take() takeFirst()
poll(time, unit) pollLast(time, unit)
element() getFirst()
peek() peekFirst()

7.4 Implementation of BlockingDeque

Since BlockingDeque is an interface, you need to use one of its many implementation classes if you want to use it. The Java.util.Concurrent package provides an implementation class for the following BlockingDeque interface: LinkedBlockingDeque

7.5 BlockingDeque code example

Here is a short code example of how to use the BlockingDeque method:

BlockingDeque<String> deque = new LinkedBlockingDeque<String>();  

deque.addFirst("1");  
deque.addLast("2");  

String two = deque.takeLast();  
String one = deque.takeFirst();  1234567
Copy the code

8. Chain blocks the LinkedBlockingDeque

The LinkedBlockingDeque class implements the BlockingDeque interface.

A deque is short for “Double Ended Queue”. So, a double-endian queue is a queue where you can insert or extract elements from either end.

LinkedBlockingDeque is a two-ended queue, and when it is empty, a thread trying to extract data from it will block, regardless of which end it is trying to extract data from.

Here is an example of LinkedBlockingDeque instantiation and use:

BlockingDeque<String> deque = new LinkedBlockingDeque<String>();  

deque.addFirst("1");  
deque.addLast("2");  

String two = deque.takeLast();  
String one = deque.takeFirst();  1234567
Copy the code

9. Create ConcurrentMap

9.1 Java. Util. Concurrent. The ConcurrentMap

Java. Util. Concurrent. The ConcurrentMap interface represents a can access to other people (insert and extract) concurrent processing of Java. Util. The Map. ConcurrentMap has some additional atomic methods in addition to those inherited from its parent interface, java.util.map.

9.2 Implementation of ConcurrentMap

Since ConcurrentMap is an interface, you need to use one of its implementation classes if you want to use it. The java.util.Concurrent package has the following implementation classes for the ConcurrentMap interface: ConcurrentHashMap

9.3 ConcurrentHashMap

ConcurrentHashMap is similar to the java.util.HashTable class, but ConcurrentHashMap provides better concurrency performance than HashTable. ConcurrentHashMap does not lock the entire Map when you read objects from it. In addition, ConcurrentHashMap does not lock the entire Map when you write objects to it. Internally, it locks only the parts of the Map that are being written.

Another difference is that at the time of be traversed, even ConcurrentHashMap is changed, it will not throw ConcurrentModificationException. Although Iterator is not designed for simultaneous use by multiple threads.

See the official documentation for more details on ConcurrentMap and ConcurrentHashMap.

9.4 the ConcurrentMap example

Here is an example of how to use the ConcurrentMap interface. This example uses the ConcurrentHashMap implementation class:

ConcurrentMap concurrentMap = new ConcurrentHashMap();  

concurrentMap.put("key"."value");  

Object value = concurrentMap.get("key");  12345
Copy the code

Concurrent navigation map ConcurrentNavigableMap

Java. Util. Concurrent ConcurrentNavigableMap is a support concurrent access to Java. Util. NavigableMap, it can also make it a child of the map has the ability of concurrent access. A “subMap” is a map returned by methods such as headMap(), subMap(), tailMap(), and so on.

In this section, we will look at the methods added to ConcurrentNavigableMap.

10.1 headMap ()

The headMap(T toKey) method returns a submap containing keys smaller than the given toKey. If you make changes to elements in the original map, those changes will affect elements in the child map.

The following example demonstrates the use of the headMap() method:

ConcurrentNavigableMap map = new ConcurrentSkipListMap();  

map.put("1"."one");  
map.put("2"."two");  
map.put("3"."three");  

ConcurrentNavigableMap headMap = map.headMap("2");  1234567
Copy the code

The headMap will point to a ConcurrentNavigableMap with only the key “1”, because this is the only key less than “2”. Refer to the Java documentation for details on how this method and its overloaded versions work.

10.2 tailMap ()

The tailMap(T fromKey) method returns a submap containing keys not smaller than the given fromKey. If you make changes to elements in the original map, those changes will affect elements in the child map.

The following example demonstrates the use of the tailMap() method:

ConcurrentNavigableMap map = new ConcurrentSkipListMap();  

map.put("1"."one");  
map.put("2"."two");  
map.put("3"."three");  

ConcurrentNavigableMap tailMap = map.tailMap("2");  1234567
Copy the code

TailMap will have keys “2” and “3” because they are not less than the given key “2”. Refer to the Java documentation for details on how this method and its overloaded versions work.

10.3 the subMap ()

The subMap() method returns the subMap of the original map with a key between from(included) and to (not included). The following is an example:

ConcurrentNavigableMap map = new ConcurrentSkipListMap();  

map.put("1"."one");  
map.put("2"."two");  
map.put("3"."three");  

ConcurrentNavigableMap subMap = map.subMap("2"."3");  1234567
Copy the code

The returned submap contains only the key “2”, because it is the only one that is not less than “2” and less than “3”.

10.4 More Methods

The ConcurrentNavigableMap interface has several other methods available, such as:

  • descendingKeySet()
  • descendingMap()
  • navigableKeySet()

Refer to the official Java documentation for more information on these methods.

11. Atresia CountDownLatch

Java. Util. Concurrent CountDownLatch is a concurrent structure, which allows one or more threads waiting for a series of complete specified operation. CountDownLatch is initialized with a given number of latches. CountDown () decreases this amount by one each time it is called. By calling one of the await() methods, the thread can block until this amount reaches zero.

Here is a simple example. Decrementer calls countDown() three times before the Waiter in the wait is released from the await() call.

CountDownLatch latch = new CountDownLatch(3);  

Waiter      waiter      = new Waiter(latch);  
Decrementer decrementer = new Decrementer(latch);  

new Thread(waiter).start();  
new Thread(decrementer).start();  

Thread.sleep(4000);  

public class Waiter implements Runnable{  

    CountDownLatch latch = null;  

    public Waiter(CountDownLatch latch) {  
        this.latch = latch;  
    }  

    public void run(a) {  
        try {  
            latch.await();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  

        System.out.println("Waiter Released"); }}public class Decrementer implements Runnable {  

    CountDownLatch latch = null;  

    public Decrementer(CountDownLatch latch) {  
        this.latch = latch;  
    }  

    public void run(a) {  

        try {  
            Thread.sleep(1000);  
            this.latch.countDown();  

            Thread.sleep(1000);  
            this.latch.countDown();  

            Thread.sleep(1000);  
            this.latch.countDown();  
        } catch(InterruptedException e) { e.printStackTrace(); }}}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
Copy the code

12. The fence CyclicBarrier

Java. Util. Concurrent. The CyclicBarrier class is a kind of synchronous mechanism, it can be to deal with some thread synchronization algorithm. In other words, it’s a fence that all threads have to wait until they get there, and then they can do something else. The illustration is as follows:

Two threads wait for each other at the fence. Two threads can wait on each other by calling the await() method of the CyclicBarrier object. Once N threads are waiting for a CyclicBarrier to complete, all threads are released to continue running.

12.1 Create a CyclicBarrier

When creating a CyclicBarrier you need to define how many threads are waiting for the barrier before being released. Example of creating a CyclicBarrier:

CyclicBarrier barrier = new CyclicBarrier(2);  1
Copy the code

12.2 Waiting for a CyclicBarrier

Here’s how to make a thread wait for a CyclicBarrier:

barrier.await();  1
Copy the code

Of course, you can also set a timeout for waiting threads. After the timeout period is exceeded, the thread is released even if the condition of N threads waiting for a CyclicBarrier has not been reached. Here is an example of defining a timeout:

barrier.await(10, TimeUnit.SECONDS);  1
Copy the code

A thread waiting on a CyclicBarrier can be freed if any of the following conditions are met:

  • The last thread also reaches CyclicBarrier(calls await())
  • The current thread is interrupted by another thread (other threads call the thread’s interrupt() method).
  • Other threads waiting for the fence are interrupted
  • Other threads waiting for the fence are released due to timeout
  • An external thread calls the CyclicBarrier. Reset () method of the fence

12.3 CyclicBarrier action

A CyclicBarrier supports a fence action, which is an instance of Runnable that will be executed once the last thread waiting for the fence arrives. You can pass the Runnable barrier action to a CyclicBarrier constructor:

Runnable      barrierAction = ... ;  
CyclicBarrier barrier       = new CyclicBarrier(2, barrierAction);  12
Copy the code

12.4 CyclicBarrier sample

The following code demonstrates how to use CyclicBarrier:

Runnable barrier1Action = new Runnable() {  
    public void run(a) {  
        System.out.println("BarrierAction 1 executed "); }}; Runnable barrier2Action =new Runnable() {  
    public void run(a) {  
        System.out.println("BarrierAction 2 executed "); }}; CyclicBarrier barrier1 =new CyclicBarrier(2, barrier1Action);  
CyclicBarrier barrier2 = new CyclicBarrier(2, barrier2Action);  

CyclicBarrierRunnable barrierRunnable1 =  new CyclicBarrierRunnable(barrier1, barrier2);  

CyclicBarrierRunnable barrierRunnable2 =  new CyclicBarrierRunnable(barrier1, barrier2);  

new Thread(barrierRunnable1).start();  
new Thread(barrierRunnable2).start();  1234567891011121314151617181920
Copy the code

CyclicBarrierRunnable class:

public class CyclicBarrierRunnable implements Runnable{  

    CyclicBarrier barrier1 = null;  
    CyclicBarrier barrier2 = null;  

    public CyclicBarrierRunnable( CyclicBarrier barrier1, CyclicBarrier barrier2) {  

        this.barrier1 = barrier1;  
        this.barrier2 = barrier2;  
    }  

    public void run(a) {  
        try {  
            Thread.sleep(1000);  
            System.out.println(Thread.currentThread().getName() +  
                                " waiting at barrier 1");  
            this.barrier1.await();  

            Thread.sleep(1000);  
            System.out.println(Thread.currentThread().getName() +  
                                " waiting at barrier 2");  
            this.barrier2.await();  

            System.out.println(Thread.currentThread().getName() +  
                                " done!");  

        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } catch(BrokenBarrierException e) { e.printStackTrace(); }}}1234567891011121314151617181920212223242526272829303132333435
Copy the code

The above code console output is as follows. Note that the timing of each thread writing to the console may be different from what you actually do. For example, sometimes thread-0 prints first, and sometimes thread-1 prints first.

Thread-0 waiting at barrier 1
Thread-1 waiting at barrier 1
BarrierAction 1 executed
Thread-1 waiting at barrier 2
Thread-0 waiting at barrier 2
BarrierAction 2 executed
Thread-0 done!
Thread-1 done! 12345678Copy the code

13. The switch is used

Java. Util. Concurrent. Exchanger class represents a kind of two threads will and points can be exchanged objects. This mechanism is illustrated as follows:

Two threads are exchanged through a NON-recovery object.

The exchange action is performed by one of the two Exchange () methods, which is non-recoverable. Here’s an example:

Exchanger exchanger = new Exchanger();  

ExchangerRunnable exchangerRunnable1 =  
        new ExchangerRunnable(exchanger, "A");  

ExchangerRunnable exchangerRunnable2 =  
        new ExchangerRunnable(exchanger, "B");  

new Thread(exchangerRunnable1).start();  
newThread(exchangerRunnable2).start(); ExchangerRunnable code: Javapublic class ExchangerRunnable implements Runnable{  

    Exchanger exchanger = null;  
    Object    object    = null;  

    public ExchangerRunnable(Exchanger exchanger, Object object) {  
        this.exchanger = exchanger;  
        this.object = object;  
    }  

    public void run(a) {  
        try {  
            Object previous = this.object;  

            this.object = this.exchanger.exchange(this.object);  

            System.out.println(  
                    Thread.currentThread().getName() +  
                    " exchanged " + previous + " for " + this.object  
            );  
        } catch(InterruptedException e) { e.printStackTrace(); }}}12345678910111213141516171819202122232425262728293031323334353637
Copy the code

Output of the above program:

Thread-0 exchanged A for B
Thread-1 exchanged B for A12
Copy the code

14. Semaphore

Java. Util. Concurrent Semaphore class is a counting Semaphore. This means it has two main approaches:

acquire()
release()12
Copy the code

The count semaphore is initialized with a specified number of permissions. Each time acquire() is called, a license is fetched by the calling thread. Each time release() is called, a license is returned to the semaphore. Thus, a maximum of N threads can pass through the acquire() method without any release() calls, N being the specified number of permits at the time the semaphore is initialized. These permissions are just a simple counter. There’s nothing fancy here.

14.1 Semaphore usage

Semaphores have two main uses:

  • Protects an important part from more than N threads at a time
  • Sends signals between two threads

14.2 Protect important parts

If you use a semaphore to protect an important part, code trying to access that part will usually try to obtain a license first, then access the important part (the code block), execute, and then release the license. Like this:

Semaphore semaphore = new Semaphore(1);  

//critical section  semaphore.acquire(); . semaphore.release();12345678
Copy the code

14.3 Sending signals between threads

If you are using a semaphore to transmit signals between two threads, you should normally call acquire() with one thread and release() with the other.

If no licenses are available, the acquire() call will block until one license is released by another thread. Similarly, a release() call will block if no more permissions are released to the semaphore.

This allows you to coordinate multiple threads. For example, if thread 1 calls acquire() after inserting an object into a shared list, and thread 2 calls release() before fetching an object from that list, you have created a blocking queue. The number of permissions available in the semaphore is equal to the number of elements that the blocking queue can hold.

14.4 fair

There is no guarantee that threads will be fairly licensed from semaphores. That is, there is no guarantee that the first thread to call acquire() will be the first thread to acquire a license. If the first thread blocks while waiting for a license and a license is released just as the second thread comes to ask for one, it may get the license before the first thread. If you want to enforce fairness, the Semaphore class has a constructor with a Boolean parameter that tells Semaphore whether to enforce fairness. Enforcing fairness affects concurrency performance, so don’t enable it unless you really need it.

Here is an example of how to create a Semaphore in fair mode:

Semaphore semaphore = new Semaphore(1.true);  1
Copy the code

14.5 More Methods

Java. Util. Concurrent. The Semaphore classes and there are many methods, such as:

  • availablePermits()
  • acquireUninterruptibly()
  • drainPermits()
  • hasQueuedThreads()
  • getQueuedThreads()
  • tryAcquire()

Refer to the Java documentation for details of these methods.

ExecutorService 15

Java. Util. Concurrent. The ExecutorService interface represents an asynchronous execution mechanism, enabled us to perform a task in the background. So an ExecutorService is a lot like a thread pool. In effect, the ExecutorService implementation that exists in the java.util.Concurrent package is a thread pool implementation.

15.1 the ExecutorService example

Here is a simple ExecutorService example:

ExecutorService executorService = Executors.newFixedThreadPool(10);  

executorService.execute(new Runnable() {  
    public void run(a) {  
        System.out.println("Asynchronous task"); }}); executorService.shutdown();123456789
Copy the code

Start by creating an ExecutorService using the newFixedThreadPool() factory method. A pool of ten threads is created to perform tasks. An anonymous implementation class of the Runnable interface is then passed to the execute() method. This will cause a thread within the ExecutorService to execute the Runnable.

15.2 Task Delegation

The following diagram illustrates how a thread can delegate a task to an ExecutorService for asynchronous execution:

A thread delegates a task to an ExecutorService for asynchronous execution. Once the thread delegates tasks to the ExecutorService, the thread continues its own execution, independent of the execution of the task.

15.3 the ExecutorService implementation

Since ExecutorService is an interface, you need to use one of its implementation classes if you want to use it. The Java.util. concurrent package provides the following implementation classes for the ExecutorService interface:

  • ThreadPoolExecutor
  • ScheduledThreadPoolExecutor

15.4 Creating an ExecutorService

The creation of the ExecutorService depends on the specific implementation you use. But you can also create ExecutorService instances by using the Executors Factory class. Here are a few examples of creating ExecutorService instances:

ExecutorService executorService1 = Executors.newSingleThreadExecutor();  
ExecutorService executorService2 = Executors.newFixedThreadPool(10);  
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);  123

Copy the code

15.5 the ExecutorService use

There are several different ways to delegate tasks to the ExecutorService:

  • execute(Runnable)
  • submit(Runnable)
  • submit(Callable)
  • InvokeAny (…).
  • InvokeAll (…).

Let’s take a look at each of these methods.

15.6 the execute (Runnable)

The execute(Runnable) method requires a java.lang.Runnable object and then executes it asynchronously. The following is an example of executing a Runnable with the ExecutorService:

ExecutorService executorService = Executors.newSingleThreadExecutor();  

executorService.execute(new Runnable() {  
    public void run(a) {  
        System.out.println("Asynchronous task"); }}); executorService.shutdown();123456789

Copy the code

There is no way to know the result of the Runnable being executed. You should use a Callable if necessary (described below).

15.7 submit (Runnable)

The Submit (Runnable) method also requires a Runnable implementation class, but it returns a Future object. This Future object can be used to check whether Runnable has been executed.

The following is an ExecutorService Submit () example:

Future future = executorService.submit(new Runnable() {  
    public void run(a) {  
        System.out.println("Asynchronous task"); }}); future.get();//returns null if the task has finished correctly. 1234567

Copy the code

15.8 submit (Callable)

The Submit (Callable) method is similar to the Submit (Runnable) method, except for the type of arguments it requires. A Callable instance is much like a Runnable except that its call() method returns a result. Runnable.run() cannot return a result. The results of the Callable can be retrieved from the Future object returned by the Submit (Callable) method. The following is an example ExecutorService Callable:

Future future = executorService.submit(new Callable(){  
    public Object call(a) throws Exception {  
        System.out.println("Asynchronous Callable");  
        return "Callable Result"; }}); System.out.println("future.get() = " + future.get());  12345678
Copy the code

The above code output:

Asynchronous Callable
future.get() = Callable Result12
Copy the code

15.9 invokeAny ()

The invokeAny() method requires a list of Callable or instance objects of its subinterfaces. Calling this method does not return a Future, but it does return the result of one of the Callable objects. There is no guarantee which Callable results will be returned – only that one of them has finished.

If one of the tasks finishes (or an exception is thrown), the other callables are cancelled.

Here is the sample code:

ExecutorService executorService = Executors.newSingleThreadExecutor();  

Set<Callable<String>> callables = new HashSet<Callable<String>>();  

callables.add(new Callable<String>() {  
    public String call(a) throws Exception {  
        return "Task 1"; }}); callables.add(new Callable<String>() {  
    public String call(a) throws Exception {  
        return "Task 2"; }}); callables.add(new Callable<String>() {  
    public String call(a) throws Exception {  
        return "Task 3"; }}); String result = executorService.invokeAny(callables); System.out.println("result = " + result);  

executorService.shutdown();  12345678910111213141516171819202122232425
Copy the code

The above code will print out the execution result of one of the given Callable collections. I tried it a few times on my own, and the results kept changing. Sometimes it’s “Task 1”, sometimes it’s “Task 2”, etc.

15.10 invokeAll ()

The invokeAll() method invokes all Callable objects that you pass to ExecutorService in the collection. InvokeAll () returns a series of Future objects from which you can retrieve the execution results of each Callable.

Keep in mind that a task may end due to an exception, so it may not “succeed.” There is no way to tell us which of the two endings we are going to have through a Future object.

Here is a code example:

ExecutorService executorService = Executors.newSingleThreadExecutor();  

Set<Callable<String>> callables = new HashSet<Callable<String>>();  

callables.add(new Callable<String>() {  
    public String call(a) throws Exception {  
        return "Task 1"; }}); callables.add(new Callable<String>() {  
    public String call(a) throws Exception {  
        return "Task 2"; }}); callables.add(new Callable<String>() {  
    public String call(a) throws Exception {  
        return "Task 3"; }}); List<Future<String>> futures = executorService.invokeAll(callables);for(Future<String> future : futures){  
    System.out.println("future.get = " + future.get());  
}  

executorService.shutdown();  123456789101112131415161718192021222324252627

Copy the code

15.11 the ExecutorService closed

Once you’re done with the ExecutorService, you should shut it down so that its threads are no longer running.

For example, if your app is started with a main() method and then main exits your app, it will keep running if your app has an active ExexutorService. The active threads in the ExecutorService prevent the JVM from shutting down.

To terminate the ExecutorService threads you need to call the Shutdown () method of the ExecutorService. The ExecutorService does not shut down immediately, but it does not accept new tasks, and it will shut down once all threads have completed their current tasks. All tasks submitted to the ExecutorService are executed before shutdown() is called.

If you want to shutdown the ExecutorService immediately, you can call the shutdownNow() method. This immediately tries to stop all executing tasks and ignores those that have been committed but not yet started processing. The correct execution of the execution task cannot be guaranteed. They may have been stopped, or they may have been executed.

16. Return thread: Java Callable

Java Java Callable interface. Util. Concurrent. Callable said asynchronous tasks can be performed by a separate thread. For example, you can submit a Callable object to the Java ExecutorService, which then executes it asynchronously.

16.1 Callable Interface Definition

The Java Callable interface is very simple. It contains a method called Call (). Here is the Callable interface:

public interface Callable<V> {

    V call(a) throws Exception;

}
Copy the code

Call the call() method to perform the asynchronous task. The call() method can return results. If the task is executed asynchronously, the results are typically propagated to the task creator via a Java Future. When a Callable is submitted to the ExecutorService for concurrent execution, a Future object can be used to receive the returned results.

The call() method can also throw an Exception if the task fails during execution.

16.2 Interface Implementation

Here is a simple example of implementing the Java Callable interface:

public class MyCallable implements Callable<String> {

    @Override
    public String call(a) throws Exception {
        returnString.valueOf(System.currentTimeMillis()); }}Copy the code

The implementation is very simple. The result is that the call() method returns a String of the current time. In a real application, a task might be a more complex or larger set of operations.

Typically, IO operations, such as reading or writing to a disk or network, are tasks that can be performed simultaneously. IO operations typically have long waits between reading and writing blocks of data. By performing such tasks in a separate thread, you can avoid unnecessarily blocking the main thread.

16.3 Callable and Runnable

The Java Callable interface is similar to the Java Runnable interface in that both represent tasks that can be executed concurrently by separate threads.

A Java Callable is different from a Runnable in that the Runnable interface’s run() method does not return a value, and it cannot throw checked exceptions (only runtimeexceptions).

In addition, Runnable was originally designed for long-running concurrent tasks, such as running web servers at the same time, or viewing directories for new files. The Callable interface is better suited for one-time tasks that return a single result.

17 Thread execution result: Java Future

Java Future, Java. Util. Concurrent. The Future said asynchronous calculation results. When you create an asynchronous task, treat the Future as a Java object returned by the thread. This Future object is used as a handle to the result of the asynchronous task. After the asynchronous task completes, the results can be accessed through the Future object.

Some of Java’s built-in concurrent services, such as the Java ExecutorService, return Future objects from some of their methods. In this case the ExecutorService returns a Future object when you commit and execute a Callable task.

17.1 Interface Definition

To understand how the Java Future interface works, the interface definition is:

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning)
    V       get(a);
    V       get(long timeout, TimeUnit unit);
    boolean isCancelled(a);
    boolean isDone(a);
}

Copy the code

Each of these methods will be covered in a later section – but as you can see, the Java Future interface is not that advanced.

17.2 Obtaining results from the Future

As mentioned earlier, A Java Future represents the result of an asynchronous task. To get the result, call one of the two get() methods through the Future object. Each of these get() methods returns an Object, but the return type can also be a generic return type (referring to an Object of a particular class, not just an Object). Here is an example of Future getting the result with its get() method:

Future future = ... // get Future by starting async task

// do something else, until ready to check result via Future

// get result from Future
try {
    Object result = future.get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

Copy the code

If the get() method is called before the asynchronous task completes, it blocks until the thread completes execution.

The second get() method can return after a timeout period, which you specify with the method argument. Here is an example of calling the get() version:

try {
    Object result =
        future.get(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {

} catch (ExecutionException e) {

} catch (TimeoutException e) {
    // thrown if timeout time interval passes
    // before a result is available.
}
Copy the code

The example Future above waits at most 1000 milliseconds. If no results are available within 1000 milliseconds, a TimeoutException is thrown.

17.3 Canceling a Task Using Future

You can cancel the corresponding asynchronous task by calling the Future’s Cancel () method. The asynchronous task must be implemented and executing. If not, the call cancel() will not work. Here is an example of cancelling a task with the Java Futurecancel() method:

future.cancel();
Copy the code

17.4 Checking whether the Task is complete

You can check if the asynchronous task is complete (and if the result is available) by calling the Future isDone() method. Here is an example of calling the Java Future isDone() method:

Future future = ... // Get Future from somewhere

if(future.isDone()) {
    Object result = future.get();
} else {
    // do something else
}
Copy the code

17.5 Checking whether a Task is Canceled

You can also check whether asynchronous tasks represented by Java have been canceled. You can check this by calling FutureisCancelled(). Here is an example of checking whether a task has been canceled:

Future future = ... // get Future from somewhere

if(future.isCancelled()) {

} else{}Copy the code

18. ThreadPoolExecutor

Java. Util. Concurrent ThreadPoolExecutor is an implementation of the ExecutorService interface. ThreadPoolExecutor uses threads from its internal pool to perform a given task (Callable or Runnable).

ThreadPoolExecutor contains a thread pool that can contain a different number of threads. The number of threads in the pool is determined by the following variables:

  • corePoolSize
  • maximumPoolSize

When a task is delegated to a thread pool, if the number of threads in the pool falls below corePoolSize, a new thread is created, even though there may be idle threads in the pool. If the internal task queue is full and there is at least corePoolSize running, but the number of running threads is less than maximumPoolSize, a new thread will be created to execute the task. ThreadPoolExecutor illustration:

A ThreadPoolExecutor

18.1 Creating a ThreadPoolExecutor

ThreadPoolExecutor has several constructors available. Such as:

int  corePoolSize  =    5;  
int  maxPoolSize   =   10;  
long keepAliveTime = 5000;  

ExecutorService threadPoolExecutor =  
        new ThreadPoolExecutor(  
                corePoolSize,  
                maxPoolSize,  
                keepAliveTime,  
                TimeUnit.MILLISECONDS,  
                new LinkedBlockingQueue<Runnable>()  
                );  123456789101112

Copy the code

But, unless you really need to explicitly define all the parameters for ThreadPoolExecutor, using Java. Util. Concurrent. One of the factory method of Executors class will be more convenient, as the ExecutorService mentioned in section.

19. The ScheduledExecutorService ScheduledExecutorService

Java. Util. Concurrent ScheduledExecutorService is a ExecutorService, it will delay the task execution, or fixed time interval to be executed multiple times. The task is executed asynchronously by a worker thread, not by the thread that submitted the task to ScheduledExecutorService.

19.1 ScheduledExecutorService example

Here is a simple ScheduledExecutorService example:

ScheduledExecutorService scheduledExecutorService =  
        Executors.newScheduledThreadPool(5);  

ScheduledFuture scheduledFuture =  
    scheduledExecutorService.schedule(new Callable() {  
        public Object call(a) throws Exception {  
            System.out.println("Executed!");  
            return "Called!"; }},5,  
    TimeUnit.SECONDS);  123456789101112

Copy the code

First, a ScheduledExecutorService with five built-in threads is created. An anonymous class example of the Callable interface is then created and passed to the Schedule () method. The last two parameters define that the Callable will be executed after 5 seconds.

19.2 ScheduledExecutorService implementation

Since ScheduledExecutorService is an interface, you need to use one of its implementation classes in the java.util.Concurrent package to use it. ScheduledExecutorService has the following implementation class: ScheduledThreadPoolExecutor

19.3 Creating a ScheduledExecutorService

How you create a ScheduledExecutorService depends on the implementation class you use. But you can also create an instance of ScheduledExecutorService using the Executors Factory class. Such as:

ScheduledExecutorService scheduledExecutorService =  Executors.newScheduledThreadPool(5);  1

Copy the code

19.4 ScheduledExecutorService use

Once you have created a ScheduledExecutorService, you can call it with the following methods:

  • schedule (Callable task, long delay, TimeUnit timeunit)
  • schedule (Runnable task, long delay, TimeUnit timeunit)
  • scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
  • scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

Let’s take a quick look at these methods.

schedule (Callable task, long delay, TimeUnit timeunit)1

Copy the code

This method plans the specified Callable to execute after the given delay. This method returns a ScheduledFuture that you can cancel before it is executed or get the result after it is executed. Here’s an example:

ScheduledExecutorService scheduledExecutorService =  
        Executors.newScheduledThreadPool(5);  

ScheduledFuture scheduledFuture =  
    scheduledExecutorService.schedule(new Callable() {  
        public Object call(a) throws Exception {  
            System.out.println("Executed!");  
            return "Called!"; }},5,  
    TimeUnit.SECONDS);  

System.out.println("result = " + scheduledFuture.get());  

scheduledExecutorService.shutdown();  12345678910111213141516

Copy the code

Sample output:

Executed!
result = Called!
123
schedule (Runnable task, long delay, TimeUnit timeunit)1
Copy the code

Except that Runnable cannot return a result, this method works just like the version of the method that takes a Callable as an argument, so scheduledFuture.get () returns null after the task is done.

scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)1
Copy the code

This method plans a task that will be performed periodically. The task will be executed after the first initialDelay and then repeated after each period.

If the execution of a given task throws an exception, the task is no longer executed. If there are no exceptions, the task will continue to loop until ScheduledExecutorService is shut down. If a task takes longer than the planned interval, the next execution will start at the end of the current execution. Scheduled tasks are not executed by multiple threads at the same time.

scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)1
Copy the code

This method is very similar to scheduleAtFixedRate() except that period is interpreted differently.

In the scheduleAtFixedRate() method, period is interpreted as the interval between the start of the previous execution and the start of the next execution.

In this method, period is interpreted as the interval between the end of the previous execution and the end of the next execution. The delay is therefore the interval between the end of execution, not the beginning.

19.5 ScheduledExecutorService closed

As with ExecutorService, you need to turn ScheduledExecutorService off after you finish using it. Otherwise it will cause the JVM to continue running even if all other threads have been shut down.

You can shutdown ScheduledExecutorService using the shutdown() or shutdownNow() methods inherited from the ExecutorService interface. See the ExecutorService Shutdown section for more information.

20. Use ForkJoinPool to fork and merge

ForkJoinPool was introduced in Java 7. It is similar to ExecutorService, except for one difference. ForkJoinPool makes it easy to break tasks into smaller tasks that are submitted to ForkJoinPool. The task can continue to be split into smaller subtasks as long as it can still be split. It may sound abstract, so in this section we explain how ForkJoinPool works and how task splitting works.

20.1 Bifurcation and merger explanation

Before we look at ForkJoinPool let’s briefly explain how forks and mergers work. The fork and merge principle involves two recursive steps. The two steps are fork step and merge step respectively.

20.2 y

A task that uses the bifurcation and merge principles can branch itself into smaller subtasks that can be executed concurrently. As shown below:

By dividing itself into multiple subtasks, each subtask can be executed in parallel by a different CPU, or by a different thread on the same CPU.

It only makes sense to divide a task into subtasks if it is too large. Splitting tasks into subtasks is expensive, so for small tasks, this splitting can be more expensive than executing each subtask concurrently.

The threshold at which it makes sense to split a task into subtasks is also known as a threshold. It depends on each task’s determination of the meaningful threshold. A lot depends on the kind of work it has to do.

20.3 combined

When a task splits itself into subtasks, the task enters a state of waiting for the completion of all subtasks.

Once the subtask completes, the task can combine all the results into the same result. The illustration is as follows:

Of course, not all types of tasks return a result. If the task does not return a result, it simply waits for all subtasks to complete. There is no need to merge the results.

20.4 ForkJoinPool

ForkJoinPool is a special thread pool designed to work well with forking and merging tasks. ForkJoinPool also in Java. Util. Concurrent package, the full class called Java. Util. Concurrent. ForkJoinPool.

20.5 Create a ForkJoinPool

You can create a ForkJoinPool with its constructor child. As an argument passed to the ForkJoinPool constructor child, you can define the desired level of parallelism. The parallel level indicates the number of threads or cpus that you want to pass to the ForkJoinPool. The following is a ForkJoinPool example:

ForkJoinPool forkJoinPool = new ForkJoinPool(4);  1
Copy the code

This example creates a ForkJoinPool with parallelism level 4.

20.6 Submit a task to ForkJoinPool

Submit tasks to ForkJoinPool just as you submit tasks to the ExecutorService. You can submit two types of tasks. One has no return value (an “action”) and the other has a return value (a “task”). Both types are represented by RecursiveAction and RecursiveTask, respectively. Here’s how to use these two types of tasks and how to submit them.

20.7 RecursiveAction

RecursiveAction is a type of task that does not return any value. It just does some work, like writing data to disk, and then exits. A RecursiveAction can break its work into smaller pieces so that they can be executed by separate threads or cpus. You can implement a RecursiveAction by inheritance. The following is an example:

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.RecursiveAction;  

public class MyRecursiveAction extends RecursiveAction {  

    private long workLoad = 0;  

    public MyRecursiveAction(long workLoad) {  
        this.workLoad = workLoad;  
    }  

    @Override  
    protected void compute(a) {  

        //if work is above threshold, break tasks up into smaller tasks  
        if(this.workLoad > 16) {  
            System.out.println("Splitting workLoad : " + this.workLoad);  

            List<MyRecursiveAction> subtasks =  
                new ArrayList<MyRecursiveAction>();  

            subtasks.addAll(createSubtasks());  

            for(RecursiveAction subtask : subtasks){ subtask.fork(); }}else {  
            System.out.println("Doing workLoad myself: " + this.workLoad); }}private List<MyRecursiveAction> createSubtasks(a) {  
        List<MyRecursiveAction> subtasks =  
            new ArrayList<MyRecursiveAction>();  

        MyRecursiveAction subtask1 = new MyRecursiveAction(this.workLoad / 2);  
        MyRecursiveAction subtask2 = new MyRecursiveAction(this.workLoad / 2);  

        subtasks.add(subtask1);  
        subtasks.add(subtask2);  

        returnsubtasks; }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647

Copy the code

The example is simple. MyRecursiveAction passes a fictitious workLoad as an argument to its constructor. If the workLoad is higher than a specific threshold, the work is divided into several sub-jobs, which continue to be divided. If the workLoad falls below a specific threshold, the work will be performed by MyRecursiveAction itself.

You can plan a MyRecursiveAction execution like this:

MyRecursiveAction myRecursiveAction = new MyRecursiveAction(24);  

forkJoinPool.invoke(myRecursiveAction);  123

Copy the code

20.8 RecursiveTask

A RecursiveTask is a task that returns a result. It can divide its work into smaller tasks and combine the execution results of these sub-tasks into a collective result. There can be several levels of segmentation and merging. Here is an example RecursiveTask:

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.RecursiveTask;  


public class MyRecursiveTask extends RecursiveTask<Long> {  

    private long workLoad = 0;  

    public MyRecursiveTask(long workLoad) {  
        this.workLoad = workLoad;  
    }  

    protected Long compute(a) {  

        //if work is above threshold, break tasks up into smaller tasks  
        if(this.workLoad > 16) {  
            System.out.println("Splitting workLoad : " + this.workLoad);  

            List<MyRecursiveTask> subtasks =  
                new ArrayList<MyRecursiveTask>();  
            subtasks.addAll(createSubtasks());  

            for(MyRecursiveTask subtask : subtasks){  
                subtask.fork();  
            }  

            long result = 0;  
            for(MyRecursiveTask subtask : subtasks) {  
                result += subtask.join();  
            }  
            return result;  

        } else {  
            System.out.println("Doing workLoad myself: " + this.workLoad);  
            return workLoad * 3; }}private List<MyRecursiveTask> createSubtasks(a) {  
        List<MyRecursiveTask> subtasks =  
        new ArrayList<MyRecursiveTask>();  

        MyRecursiveTask subtask1 = new MyRecursiveTask(this.workLoad / 2);  
        MyRecursiveTask subtask2 = new MyRecursiveTask(this.workLoad / 2);  

        subtasks.add(subtask1);  
        subtasks.add(subtask2);  

        returnsubtasks; }}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
Copy the code

This example is similar to the RecursiveAction example, except that one result is returned. The MyRecursiveTask class inherits from RecursiveTask, which means that it will return a result of type Long.

The MyRecursiveTask example also splits work into subtasks and plans execution of those subtasks through the fork() method.

In addition, this example collects the results returned by each subtask by calling their join() method. The results of the subtasks are then combined into a larger result and eventually returned. For different levels of recursion, the result merging of this subtask may occur recursively.

You can plan a RecursiveTask like this:

MyRecursiveTask myRecursiveTask = new MyRecursiveTask(128);  

long mergedResult = forkJoinPool.invoke(myRecursiveTask);  

System.out.println("mergedResult = " + mergedResult);   12345
Copy the code

Note how the final execution result is obtained through a call to forkJoinPool.invoke ().

20.9 ForkJoinPool comments

It seems that not everyone is happy with ForkJoinPool in Java 7: A Java fork: The Scourge of Merger. Read this article before you plan to use ForkJoinPool in your own projects.

21. Lock the Lock

Java. Util. Concurrent. The locks. The Lock is a similar to synchronized blocks of thread synchronization mechanism. But Lock is more flexible and subtle than synchronized blocks. By the way, I describe how to implement your own locks in my Java Concurrency Guide.

21.1 Java Lock examples

Since Lock is an interface, you need to use one of its implementation classes in your program to use it. Here’s a simple example:

Lock lock = new ReentrantLock();  

lock.lock();  

//critical section  

lock.unlock();  1234567
Copy the code

First, a Lock object is created. Its lock() method is then called. The lock instance is now locked. Any other thread that calls the lock() method will block until the thread that locked the lock instance calls unlock(). Finally, unlock() is called, the lock object is unlocked, and it can be locked by other threads.

21.2 Java Lock implementation

Java. Util. Concurrent. The locks package provides the following for the Lock interface implementation class: already

21.3 Key differences between Lock and synchronized code blocks

The main differences between a Lock object and a synchronized block are:

  • Synchronized blocks do not guarantee the sequence of threads entering the access wait.
  • You cannot pass any parameters to the entry of a synchronized block. Therefore, it is impossible to set an access wait timeout for synchronized code blocks.
  • Synchronized blocks must be fully contained within a single method. A Lock object can place its Lock () and unlock() calls in separate methods.

21.4 The method of the Lock

The Lock interface has the following main methods:

  • lock()

Lock () locks the lock instance. If the Lock instance is locked, the thread calling the Lock () method will block until the Lock instance is unlocked.

  • lockInterruptibly()

The lockInterruptibly() method will be locked by the calling thread unless the thread is interrupted. In addition, if a thread enters a blocking wait while using the method to Lock the Lock object, and it is interrupted, the thread will exit the method call.

  • tryLock()

The tryLock() method attempts to Lock the Lock instance immediately. This method returns true if the Lock succeeded and false if the Lock instance was locked. This method never blocks.

  • tryLock(long timeout, TimeUnit timeUnit)

TryLock (long timeout, TimeUnit TimeUnit) works like the tryLock() method, except that it waits for a given timeout before abandoning the Lock.

  • unlock()

The unlock() method unlocks the Lock instance. A Lock implementation will only allow the thread that locks the object to call this method. Calls to unlock() by other threads (threads that have not locked the Lock object) will throw an unchecked exception.

22. Read/write lock ReadWriteLock

Java. Util. Concurrent. The locks. ReadWriteLock read-write lock is an advanced thread locking mechanism. It allows multiple threads to read a particular resource at the same time, but only one thread can write to it at a time.

The idea behind read-write locks is that multiple threads can read a shared resource without causing concurrency problems. Concurrency problems occur when read and write operations on a shared resource are performed simultaneously, or multiple write operations are performed concurrently.

This section only discusses Java’s built-in ReadWriteLock. If you want to understand the implementation behind ReadWriteLock, please refer to the read-write Lock section in my Guide to Java Concurrency topic.

22.1 ReadWriteLock Lock rule

A thread locks ReadWriteLock before reading or writing to a protected resource: Read lock: If no write thread locks ReadWriteLock and no write thread requests (but has not acquired) a write lock. Therefore, the lock can be locked by multiple read threads.

Write lock: If there is no read or write operation. Therefore, only one thread can lock the lock at write time.

22.2 ReadWriteLock implementation

ReadWriteLock is an interface, and if you want to use it, you have to use one of its implementation classes. Java. Util. Concurrent. The locks package provides the ReadWriteLock interface implementation class: ReentrantReadWriteLock

22.3 ReadWriteLock code examples

Here is a simple example of how to create ReadWriteLock and how to use it for read/write locking:

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();  

readWriteLock.readLock().lock();  

// multiple readers can enter this section  
// if not locked for writing, and not writers waiting  
// to lock for writing.  

readWriteLock.readLock().unlock();  

readWriteLock.writeLock().lock();  

// only one writer can enter this section,  
// and only if no threads are currently reading.  

readWriteLock.writeLock().unlock();  12345678910111213141516

Copy the code

Notice how ReadWriteLock is used to hold both lock instances. One protects read access and one protects write access.

23. AtomicBoolean

The AtomicBoolean class gives us a Boolean value that can be read and written atomically, and it has some advanced atomic operations, such as compareAndSet(). AtomicBoolean classes in Java. Util. Concurrent. Atomic package, the full name of the class is for Java. Util. Concurrent. Atomic. AtomicBoolean. The AtomicBoolean described in this section is in the Java 8 version, not the Java 5 version where it was first introduced.

The design philosophy behind AtomicBoolean is explained in the Comparison and Interchange section of my Java Concurrency Guide.

23.1 Creating an AtomicBoolean

You can create an AtomicBoolean like this:

AtomicBoolean atomicBoolean = new AtomicBoolean();  1
Copy the code

The above example creates an AtomicBoolean with a default value of false.

If you want to set an explicit initial value for the AtomicBoolean instance, you can pass the initial value to the AtomicBoolean constructor:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  1
Copy the code

23.2 Obtaining the AtomicBoolean value

You can get an AtomicBoolean value by using the get() method. The following is an example:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  

boolean value = atomicBoolean.get();  123
Copy the code

The value variable will be true after the above code is executed.

23.3 Setting AtomicBoolean

You can set an AtomicBoolean value by using the set() method. The following is an example:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  

atomicBoolean.set(false);  123
Copy the code

The AtomicBoolean value is false after the above code is executed.

23.4 Swapping AtomicBoolean values

You can swap the values of an AtomicBoolean instance with the getAndSet() method. The getAndSet() method returns AtomicBoolean’s current value and sets a new value for AtomicBoolean. The following is an example:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  
boolean oldValue = atomicBoolean.getAndSet(false);  12

Copy the code

The oldValue variable will be true after the above code executes, and the atomicBoolean instance will hold a false value. The code successfully swaps the AtomicBoolean current value ture to false.

23.5 Compare and set AtomicBoolean values

The compareAndSet() method allows you to compare the current value of AtomicBoolean with an expected value. If the current value equals the expected value, AtomicBoolean will be assigned a new value. The compareAndSet() method is atomic, so a single thread executes it at the same time. So the compareAndSet() method can be used for some simple implementations of locklike synchronization.

Here is an example of compareAndSet() :

AtomicBoolean atomicBoolean = new AtomicBoolean(true);  

boolean expectedValue = true;  
boolean newValue      = false;  

boolean wasNewValueSet = atomicBoolean.compareAndSet(expectedValue, newValue);  123456
Copy the code

This example compares the current value of AtomicBoolean to the true value and updates the AtomicBoolean value to false if it is equal.

24. AtomicInteger

The AtomicInteger class gives us an int variable that can perform atomic reads and writes, and it also contains a series of advanced atomic operations, such as compareAndSet(). AtomicInteger classes in Java. Util. Concurrent. Atomic package, so the full class called Java. Util. Concurrent. Atomic. AtomicInteger. The AtomicInteger described in this section is in the Java 8 version, not the Java 5 version where it was first introduced.

The design philosophy behind AtomicInteger is explained in the Comparison and Interchange section of my Java Concurrency Guide.

24.1 Creating an AtomicInteger

An example of creating an AtomicInteger is as follows:

AtomicInteger atomicInteger = new AtomicInteger();  1
Copy the code

This example creates an AtomicInteger with an initial value of 0. If you want to create an AtomicInteger with a given initial value, you can do this:

AtomicInteger atomicInteger = new AtomicInteger(123);  1

Copy the code

This example passes 123 as a parameter to the constructor of AtomicInteger, which sets the initial value of the AtomicInteger instance to 123.

24.2 Obtaining the AtomicInteger value

You can use the get() method to get the value of the AtomicInteger instance. The following is an example:

AtomicInteger atomicInteger = new AtomicInteger(123);  
int theValue = atomicInteger.get();  12

Copy the code

24.3 Setting AtomicInteger

You can reset the value of AtomicInteger using the set() method. Here is an example of atomicInteger.set () :

AtomicInteger atomicInteger = new AtomicInteger(123);  
atomicInteger.set(234);  12

Copy the code

The above example creates an AtomicInteger with an initial value of 123, which is updated to 234 in the second line.

24.4 Compare and set AtomicInteger values

The AtomicInteger class also passes an atomic compareAndSet() method. This method compares the current value of the AtomicInteger instance to the expected value, and if they are equal, sets a new value for the AtomicInteger instance. AtomicInteger.com pareAndSet () code sample:

AtomicInteger atomicInteger = new AtomicInteger(123);  

int expectedValue = 123;  
int newValue      = 234;  
atomicInteger.compareAndSet(expectedValue, newValue);  12345

Copy the code

This example starts by creating an AtomicInteger instance with an initial value of 123. AtomicInteger is then compared to the expected value of 123, and if it is equal, the AtomicInteger value is updated to 234.

24.5 Increasing the AtomicInteger value

The AtomicInteger class contains methods by which you can increment the value of AtomicInteger and get its value. These methods are as follows:

  • addAndGet()
  • getAndAdd()
  • getAndIncrement()
  • incrementAndGet()

The first addAndGet() method adds a value to AtomicInteger and returns the added value. The getAndAdd() method adds a value to AtomicInteger, but returns the value of the previous AtomicInteger. Which one to use depends on your application scenario. Here are examples of both methods:

AtomicInteger atomicInteger = new AtomicInteger();  
System.out.println(atomicInteger.getAndAdd(10));  
System.out.println(atomicInteger.addAndGet(10));  123
Copy the code

This example prints 0 and 20. In this example, the second line gets the AtomicInteger value before adding 10. The value before we add 10 is 0. The third line adds another 10 to the value of AtomicInteger and returns the value after the addition operation. The value is now 20.

You can of course use these two methods to add negative values to AtomicInteger. The result is actually a subtraction operation.

The getAndIncrement() and incrementAndGet() methods are similar to getAndAdd() and addAndGet(), but only increment the AtomicInteger value by one at a time.

24.6 Decrease the AtomicInteger value

The AtomicInteger class also provides atomistic methods for reducing the value of AtomicInteger. These methods are:

  • decrementAndGet()
  • getAndDecrement()

DecrementAndGet () decrements the value of AtomicInteger by one and returns the value. GetAndDecrement () also reduces the AtomicInteger value by one, but it returns the value before the reduction.

25. AtomicLong

The AtomicLong class gives us a long variable that can do atomic reads and writes, and it also contains a series of advanced atomic operations, Such as compareAndSet () AtomicLong class in Java. Util. Concurrent. Atomic package, so the full class called Java. Util. Concurrent. Atomic. AtomicLong. The AtomicLong described in this section is in the Java 8 version, not the Java 5 version where it was first introduced.

The design philosophy behind AtomicLong is explained in the Comparison and Interchange section on the topic of my Java Concurrency Guide.

Create AtomicLong create AtomicLong

AtomicLong atomicLong = new AtomicLong();  1
Copy the code

An AtomicLong will be created with an initial value of 0. If you want to create an AtomicLong with an initial value, you can:

AtomicLong atomicLong = new AtomicLong(123);  1
Copy the code

This example passes 123 as a parameter to the AtomicLong constructor, which sets the initial value of the AtomicLong instance to 123. You can get the AtomicLong value by using the get() method. AtomicLong. The get () example:

AtomicLong atomicLong = new AtomicLong(123);  

long theValue = atomicLong.get();  123

Copy the code

Set AtomicLong You can set the value of the AtomicLong instance with the set() method. An example of atomicLong.set () :

AtomicLong atomicLong = new AtomicLong(123);  

atomicLong.set(234);  123

Copy the code

This example creates a new AtomicLong with an initial value of 123 and sets its value to 234 in the second line.

25.1 Comparing and Setting the AtomicLong Value

The AtomicLong class also has an atomic compareAndSet() method. This method compares the current value of the AtomicLong instance to an expected value, and if the two are equal, sets a new value for the AtomicLong instance. AtomicLong.compareAndSet() Usage example:

AtomicLong atomicLong = new AtomicLong(123);  

long expectedValue = 123;  
long newValue      = 234;  
atomicLong.compareAndSet(expectedValue, newValue);  12345

Copy the code

This example creates an AtomicLong with an initial value of 123. The current AtomicLong value is then compared to the expected value of 123, and if it is equal, the new AtomicLong value will be 234.

25.2 Increasing the AtomicLong Value

AtomicLong has some methods that can increment the value of the AtomicLong and return its own value. These methods are as follows:

  • addAndGet()
  • getAndAdd()
  • getAndIncrement()
  • incrementAndGet()

The first method, addAndGet(), adds a number to the value of the AtomicLong and returns the increased value. The second method getAndAdd() also increments the value of the AtomicLong by a number, but returns the value of the AtomicLong before it is incremented. Which one to use depends on your own scenario. The following is an example:

AtomicLong atomicLong = new AtomicLong();  
System.out.println(atomicLong.getAndAdd(10));  
System.out.println(atomicLong.addAndGet(10));  123
Copy the code

This example prints 0 and 20. In this example, the second row gets the AtomicLong value before adding 10. The value before we add 10 is 0. The third line adds another 10 to the AtomicLong value and returns the value after the addition operation. The value is now 20.

You can also use these methods to add negative values to AtomicLong. The result is actually a subtraction operation.

The getAndIncrement() and incrementAndGet() methods are similar to getAndAdd() and addAndGet(), but only increment the AtomicLong value by one at a time.

25.3 Decrease the AtomicLong Value

The AtomicLong class also provides atomistic methods to reduce the value of the AtomicLong. These methods are:

  • decrementAndGet()
  • getAndDecrement()

DecrementAndGet () decrements the AtomicLong value by one and returns the value. GetAndDecrement () also reduces the AtomicLong value by one, but it returns the value before the reduction.

26. AtomicReference

AtomicReference provides an object reference variable that can be read and written atomically. Atomicity means that multiple threads that want to change the same AtomicReference will not cause the AtomicReference to be in an inconsistent state. AtomicReference also has a compareAndSet() method that allows you to compare the current reference to an expected value (reference) and, if equal, set a new reference inside the AtomicReference object.

26.1 Creating an AtomicReference

Create AtomicReference as follows:

AtomicReference atomicReference = new AtomicReference();  1

Copy the code

If you need to create an AtomicReference with a specified reference, you can:

String initialReference = "the initially referenced string";  
AtomicReference atomicReference = new AtomicReference(initialReference);  12
Copy the code

26.2 Creating a generic AtomicReference

You can use Java generics to create a generic AtomicReference. Example:

AtomicReference<String> atomicStringReference =  
    new AtomicReference<String>();  12
Copy the code

You can also set an initial value for the generic AtomicReference. Example:

String initialReference = "the initially referenced string";  
AtomicReference<String> atomicStringReference =  new AtomicReference<String>(initialReference);  12
Copy the code

26.3 Gets the AtomicReference reference

You can retrieve references stored in the AtomicReference using the Get () method of the AtomicReference. If your AtomicReference is non-generic, the get() method returns a reference of type Object. If generic, get() will return the type you declared when you created the AtomicReference.

Let’s start with a non-generic AtomicReference get() example:

AtomicReference atomicReference = new AtomicReference("first value referenced");  
String reference = (String) atomicReference.get();  12
Copy the code

Notice how to cast a reference returned by the get() method to a String. Example of genericized AtomicReference:

AtomicReference<String> atomicReference =   new AtomicReference<String>("first value referenced");
String reference = atomicReference.get();  12
Copy the code

The compiler knows the type of the reference, so we no longer need to cast the reference returned by get().

26.4 Setting an AtomicReference Reference

You can use the get() method to set the reference stored in the AtomicReference. If you define a non-generic AtomicReference, set() will take an Object reference. If it is a generic AtomicReference, the set() method will only accept the type you define.

AtomicReference set()

AtomicReference atomicReference = new AtomicReference();  
atomicReference.set("New object referenced");  12
Copy the code
  • This looks like non-generic and generic. The real difference is that the compiler limits the types of AtomicReference parameters you can set to a generic.

26.5 Compare and set the AtomicReference Reference

The AtomicReference class has a useful method: compareAndSet(). CompareAndSet () can compare a reference stored in the AtomicReference to a desired reference. If two references are equal (not equals(), but equals ==), A new reference will be set to the AtomicReference instance.

CompareAndSet () returns true if compareAndSet() sets a new reference to the AtomicReference. Otherwise compareAndSet() returns false.

AtomicReference compareAndSet()

String initialReference = "initial value referenced";  

AtomicReference<String> atomicStringReference =  
    new AtomicReference<String>(initialReference);  

String newReference = "new value referenced";  
boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference);  
System.out.println("exchanged: " + exchanged);  

exchanged = atomicStringReference.compareAndSet(initialReference, newReference);  
System.out.println("exchanged: " + exchanged);  1234567891011
Copy the code

This example creates a generic AtomicReference with an initial reference. ComparesAndSet () is then called twice to compare the stored value with the expected value and, if they are consistent, to set a new reference to the AtomicReference. The first time the comparison is made, the stored reference (initialReference) matches the desired reference (initialReference), so a newReference (newReference) is set to the AtomicReference, The compareAndSet() method returns true. On the second comparison, the stored reference (newReference) and the desired reference (initialReference) do not match, so the newReference is not set to the AtomicReference, and the compareAndSet() method returns false.