The java.util.concurrent package provides tools for creating concurrent applications.
2. Main componentsjava.util.concurrent contains too many features. In this article, we focus on some of the most useful utilities in this package,
For example:
- Executor
- ExecutorService
- ScheduledExecutorService
- Future
- CountDownLatch
- CyclicBarrier
- Semaphore
- ThreadFactory
- BlockingQueue
- DelayQueue
- Locks
- Phaser
2.1. Executor
The Executor is an interface that represents a task object.
If the task is to run on a new thread or the current thread, it depends on the specific implementation (where to start the call). Therefore, using this interface, the task execution process can be decoupled from the actual task execution mechanism.
One thing to note here is that the Executor does not strictly require asynchronous task execution. In the simplest case, the performer can immediately call the submitted task in the calling thread.
Create a caller instance:
public class Invoker implements Executor { @Override public void execute(Runnable r) { r.run(); } }
Call the program to perform the task.
public void execute() { Executor executor = new Invoker(); executor.execute(() -> System.out.println("execute task ...")); }
It should be noted here that if the executor cannot accept task execution, it will throw RejectedExecutionException.
2.2. ExecutorService
ExecutorService is a complete asynchronous processing solution. It manages memory queues and schedules submitted tasks based on thread availability.
To use ExecutorService, you need to create a Runnable class.
public class Task implements Runnable { @Override public void run() { // Task details } }
Now create the ExecutorService instance and assign the task. When creating, you need to specify the thread pool size.
ExecutorService executor = Executors.newFixedThreadPool(10);
If you want to create a single threaded ExecutorService instance, you can use new single thread executor (threadfactory threadfactory) to create the instance.
Once you create an actuator, you can use it to submit tasks.
public void execute() { executor.submit(new Task()); }
You can also create a Runnable instance when submitting a task.
executor.submit(() -> { new Task(); });
It also comes with two execution termination methods out of the box. The first is shutdown(); It waits for all submitted tasks to complete execution. Another method is shutdown now (), which immediately terminates all pending / executing tasks.
Another method, awaitTermination(long timeout, TimeUnit unit), forcibly blocks until all tasks complete execution after triggering the shutdown event or execution timeout, or the execution thread itself is interrupted,
try { executor.awaitTermination( 20l, TimeUnit.NANOSECONDS ); } catch (InterruptedException e) { e.printStackTrace(); }
2.3. ScheduledExecutorService
ScheduledExecutorService is an interface similar to ExecutorService, but it can perform tasks regularly.
The methods of Executor and ExecutorService are on-site scheduling and will not introduce any artificial delay. Zero or any negative value indicates that the request needs to be executed immediately.
You can use both Runnable and Callable interfaces to define tasks.
public static void execute() throws Exception { ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); Future<String> future = executorService.schedule(() -> "Hello world", 20, TimeUnit.SECONDS); ScheduledFuture<?> scheduledFuture = executorService.schedule(() -> { System.out.println("Delay 5 s implement"); }, 5, TimeUnit.SECONDS); while (future.isDone() && !future.isCancelled()) { Thread.sleep(2000); } System.out.println(future.get()); executorService.shutdown(); }
ScheduledExecutorService can also perform tasks after a given fixed delay:
executorService.scheduleAtFixedRate(() -> { // ... }, 1, 10, TimeUnit.SECONDS); executorService.scheduleWithFixedDelay(() -> { // ... }, 1, 10, TimeUnit.SECONDS);
ScheduleAtFixedRate (Runnable command, long initialDelay, long period, TimeUnit unit) method creates and executes a periodic action, which is first called after the initial delay provided, and then is called within a given time period until the service instance is closed.
The schedulewithfixeddelay (runnable command, long initialdelay, long delay, timeunit) method creates and executes a periodic action, which is called first after the provided initial delay, and repeats the given delay between the termination of execution and the call.
2.4. Future
Future is used to represent the result of an asynchronous operation. It has methods for checking whether the asynchronous operation is completed, obtaining the calculation results, etc.
More importantly, the cancel(boolean mayInterruptIfRunning) API cancels the operation and releases the executing thread. If the value of mayInterruptIfRunning is true, the thread executing the task will terminate immediately.
Otherwise, the task in progress will be allowed to complete.
public void invoke() { ExecutorService executorService = Executors.newFixedThreadPool(10); Future<String> future = executorService.submit(() -> { // Perform certain tasks Thread.sleep(10000l); return "Hello world"; }); }
You can use the following code snippet to check whether the results of Future are ready, and get the data if the calculation is completed:
if (future.isDone() && !future.isCancelled()) { try { str = future.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }
You can also specify a timeout for a given operation. If the task takes longer than this time, a TimeoutException will be thrown:
try { future.get(10, TimeUnit.SECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { e.printStackTrace(); }
2.5. CountDownLatch
CountDownLatch (introduced in JDK 5) is a utility class that blocks a set of threads until some operations are completed.
A CountDownLatch is initialized with a counter (integer type); This counter decrements as the dependent thread completes execution. However, once the counter reaches zero, other threads are released.
2.6. CyclicBarrier
CyclicBarrier works almost the same as CountDownLatch, except that it can be reused. Unlike CountDownLatch, it allows multiple threads to wait for each other using the await() method (called a barrier condition) before calling the final task.
public class CyclicBarrierTest { public static void main(String[] args) { start(); } public static void start() { CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> { // ... System.out.println("All previous tasks are completed"); }); Thread t1 = new Thread(new Task(cyclicBarrier), "T1"); Thread t2 = new Thread(new Task(cyclicBarrier), "T2"); Thread t3 = new Thread(new Task(cyclicBarrier), "T3"); if (!cyclicBarrier.isBroken()) { t1.start(); t2.start(); t3.start(); } } } class Task implements Runnable { private CyclicBarrier barrier; public Task(CyclicBarrier barrier) { this.barrier = barrier; } @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " is waiting"); barrier.await(); System.out.println(Thread.currentThread().getName() + " is released"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } }
output
T1 is waiting T2 is waiting T3 is waiting All previous tasks are completed T3 is released T1 is released T2 is released
The isBroken() method checks whether any thread was interrupted during execution. We should always perform this check before performing the actual process.
2.7. Semaphore
Semaphores are used to prevent thread level access to certain parts of a physical or logical resource. Semaphores contain a set of permissions; Whenever a thread tries to enter a critical area, it needs to check whether the semaphore is licensed.
If the permission is not available (through tryAcquire()), the thread is not allowed to jump into the critical area; However, if a license is available, access is granted and the license counter decreases.
Once the execution thread releases the critical area, the license counter is incremented again (completed by the release() method).
You can use the tryacquire (long timeout, timeunit) method to specify the timeout for obtaining access.
You can also check the number of licenses available or the number of threads waiting to get semaphores.
public class SemaphoreTest { static Semaphore semaphore = new Semaphore(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 20; i++) { Thread.sleep(500); Thread thread = new Thread(() -> { try { execute(); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.setName("Thread[" + i + "]"); thread.start(); } } public static void execute() throws InterruptedException { System.out.println("Available semaphores : " + semaphore.availablePermits()); System.out.println("Number of threads waiting to get: " + semaphore.getQueueLength()); if (semaphore.tryAcquire()) { try { System.out.println(Thread.currentThread().getName() + ": Task in progress..."); Thread.sleep(new Random().nextInt(10) * 1000); } finally { semaphore.release(); } } else { System.out.println(Thread.currentThread().getName() + ": No permission to perform tasks"); } } }
Semaphores can be used to implement data structures similar to mutexes.
2.8. ThreadFactory
ThreadFactory acts as a thread (non-existent) pool and creates new threads on demand. It eliminates the large amount of boilerplate code required to implement an efficient thread creation mechanism.
public class CustomThreadFactory implements ThreadFactory { private int threadId; private String name; public CustomThreadFactory(String name) { threadId = 1; this.name = name; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, name + "-Thread_" + threadId); System.out.println("New thread id by : " + threadId + " name by : " + t.getName()); threadId++; return t; } public static void main(String[] args) { CustomThreadFactory factory = new CustomThreadFactory( "CustomThreadFactory"); for (int i = 0; i < 10; i++) { int finalI = i; Runnable runnable = () -> System.out.println(finalI + " runable thread"); Thread t = factory.newThread(runnable); t.start(); } } }
2.9.BlockingQueue
One of the most common integration patterns in asynchronous programming is the producer consumer pattern. The java.util.concurrent package comes with a data structure called BlockingQueue -- which is very useful in these asynchronous scenarios.
2.10. DelayQueue
DelayQueue is an infinite size element blocking queue, in which an element can be pulled only when its expiration time (called user-defined delay) is completed. Therefore, the topmost element (header) will have the largest amount of delay and will be polled last.
2.11. Locks
Lock is a practical class that prevents other threads from accessing a specific code segment, except the thread currently executing it.
The main difference between Lock and Synchronized blocks is that the synchronization block is completely contained in the method; However, the lock() and unlock() operations of the Lock API can be used in different methods.
2.12. Phaser
Phaser is a more flexible solution than CyclicBarrier and CountDownLatch - it serves as a reusable barrier, and a dynamic number of threads need to wait before continuing execution. Multiple stages of execution can be coordinated, and a phaser instance can be reused for each program stage.