The java.util.concurrent
package provides a set of classes and interfaces that support multithreaded programming. This package enables developers to easily write programs that take advantage of multiple processors.
The java.util.concurrent
package contains the following items:
In this article, we will focus on how to use the java.util.concurrent
package to write parallel programs.
An Executor
is an object that executes submitted Runnable
tasks. The Executor
interface provides a way of decoupling task submission from the mechanics of how each task will be run, including queuing, scheduling, and execution.
There are a number of built-in Executor
implementations in the java.util.concurrent
package, such as ThreadPoolExecutor
and ScheduledThreadPoolExecutor
. We can also create our own Executor
implementation.
In the following example, we create a Runnable
task and submit it to an Executor
for execution:
Runnable task = () -> {
// Do something
};
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(task);
A Future
represents the result of an asynchronous computation. We can use a Future
to check if the computation is complete, and to retrieve the result of the computation.
In the following example, we use a Future
to check if a task has finished and to retrieve the result:
Future<Integer> future = executor.submit(task);
if (future.isDone()) {
int result = future.get();
}
A ThreadPool
is a collection of Threads
that can be used to execute tasks. A ThreadPool
manages the Threads
in the pool, and provides mechanisms to reuse Threads
when they are available.
In the following example, we create a ThreadPool
with four Threads
:
ExecutorService threadPool = Executors.newFixedThreadPool(4);
We can submit Runnable
tasks to the ThreadPool
for execution:
threadPool.submit(task);
When we are finished with the ThreadPool
, we can shut it down:
threadPool.shutdown();
A Lock
is a mechanism for controlling access to a shared resource. Locks can be used to implement critical sections, which are sections of code that must be executed atomically.
In the following example, we use a Lock
to control access to a shared resource:
Lock lock = new ReentrantLock();
lock.lock();
try {
// Access the shared resource
} finally {
lock.unlock();
}
An AtomicVariable
is a variable that can be updated atomically. AtomicVariable
s are used to implement critical sections.
In the following example, we use an AtomicVariable
to control access to a shared resource:
AtomicInteger counter = new AtomicInteger();
int value = counter.incrementAndGet();
The java.util.concurrent
package contains a number of concurrent collections, which are collections that can be safely used by multiple threads.
In the following example, we use a ConcurrentHashMap
to store data in a thread-safe way:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("foo", 42);
map.get("foo");
A Synchronizer
is an object that coordinates the actions of multiple threads. The java.util.concurrent
package contains a number of synchronizers, such as Semaphore
and CountDownLatch
.
In the following example, we use a CountDownLatch
to wait for a task to finish:
CountDownLatch latch = new CountDownLatch(1);
executor.submit(() -> {
try {
// Do something
latch.countDown();
} catch (InterruptedException e) {
// Handle the exception
}
});
latch.await();
In this article, we have looked at how to use the java.util.concurrent
package to write parallel programs. We have seen how to use Executors
, Futures
, ThreadPools
, Locks
, AtomicVariables
, ConcurrentCollections
, and Synchronizers
.