原文地址 http://tutorials.jenkov.com/java-util-concurrent/index.html
The Java BlockingQueue
interface in the java.util.concurrent
class represents a queue which is thread safe to put into, and take instances from. In this text I will show you how to use this BlockingQueue
.
This text will not discuss how to implement a BlockingQueue
in Java yourself. If you are interested in that, I have a text onBlocking Queues in my more theoretical Java Concurrency Tutorial.
A BlockingQueue
is typically used to have on thread produce objects, which another thread consumes. Here is a diagram that illustrates this principle:
![]() |
A BlockingQueue with one thread putting into it, and another thread taking from it. |
The producing thread will keep producing new objects and insert them into the queue, until the queue reaches some upper bound on what it can contain. It's limit, in other words. If the blocking queue reaches its upper limit, the producing thread is blocked while trying to insert the new object. It remains blocked until a consuming thread takes an object out of the queue.
The consuming thread keeps taking objects out of the blocking queue, and processes them. If the consuming thread tries to take an object out of an empty queue, the consuming thread is blocked until a producing thread puts an object into the queue.
A BlockingQueue
has 4 different sets of methods for inserting, removing and examining the elements in the queue. Each set of methods behaves differently in case the requested operation cannot be carried out immediately. Here is a table of the methods:
Throws Exception | Special Value | Blocks | Times Out | |
Insert | add(o) |
offer(o) |
put(o) |
offer(o, timeout, timeunit) |
Remove | remove(o) |
poll(o) |
take(o) |
poll(timeout, timeunit) |
Examine | element(o) |
peek(o) |
|
|
The 4 different sets of behaviour means this:
It is not possible to insert null
into a BlockingQueue
. If you try to insert null, the BlockingQueue
will throw aNullPointerException
.
It is also possible to access all the elements inside a BlockingQueue
, and not just the elements at the start and end. For instance, say you have queued an object for processing, but your application decides to cancel it. You can then call e.g.remove(o)
to remove a specific object in the queue. However, this is not done very efficiently, so you should not use these Collection
methods unless you really have to.
Since BlockingQueue
is an interface, you need to use one of its implementations to use it. Thejava.util.concurrent
package has the following implementations of the BlockingQueue
interface (in Java 6):
Click the links in the list to read more about each implementation. If a link cannot be clicked, that implementation has not yet been described. Check back again in the future, or check out the JavaDoc's for more detail.
Here is a Java BlockingQueue
example. The example uses the ArrayBlockingQueue
implementation of theBlockingQueue
interface.
First, the BlockingQueueExample
class which starts a Producer
and a Consumer
in separate threads. TheProducer
inserts strings into a shared BlockingQueue
, and the Consumer
takes 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); } }
Here is the Producer
class. Notice how it sleeps a second between each 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() { try { queue.put("1"); Thread.sleep(1000); queue.put("2"); Thread.sleep(1000); queue.put("3"); } catch (InterruptedException e) { e.printStackTrace(); } } }
Here is the Consumer
class. It just takes out the 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() { try { System.out.println(queue.take()); System.out.println(queue.take()); System.out.println(queue.take()); } catch (InterruptedException e) { e.printStackTrace(); } } }
The ArrayBlockingQueue
class implements the BlockingQueue
interface. Read the BlockingQueue
text for more information about the interface.
ArrayBlockingQueue
is a bounded, blocking queue that stores the elements internally in an array. That it is bounded means that it cannot store unlimited amounts of elements. There is an upper bound on the number of elements it can store at the same time. You set the upper bound at instantiation time, and after that it cannot be changed.
The ArrayBlockingQueue
stores the elements internally in FIFO (First In, First Out) order. The head
of the queue is the element which has been in queue the longest time, and the tail
of the queue is the element which has been in the queue the shortest time.
Here is how to instantiate and use an ArrayBlockingQueue
:
BlockingQueue queue = new ArrayBlockingQueue(1024); queue.put("1"); Object object = queue.take();
Here is a BlockingQueue
example that uses Java Generics. Notice how you can put and take String's instead of :
BlockingQueuequeue = new ArrayBlockingQueue (1024); queue.put("1"); String string = queue.take();
DelayQueue
class implements the BlockingQueue
interface. Read the BlockingQueue
text for more information about the interface.
The DelayQueue
keeps the elements internally until a certain delay has expired. The elements must implement the interface java.util.concurrent.Delayed
. Here is how the interface looks:
public interface Delayed extends ComparableThe value returned by the
getDelay()
method should be the delay remaining before this element can be released. If 0 or a negative value is returned, the delay will be considered expired, and the element released at the nexttake()
etc. call on theDelayQueue
.The
TimeUnit
instance passed to thegetDelay()
method is anEnum
that tells which time unit the delay should be returned in. TheTimeUnit
enum can take these values:DAYS HOURS MINUTES SECONDS MILLISECONDS MICROSECONDS NANOSECONDSThe
Delayed
interface also extends thejava.lang.Comparable
interface, as you can see, which means thatDelayed
objects can be compared to each other. This is probably used internally in theDelayQueue
to order the elements in the queue, so they are released ordered by their expiration time.Here is an example of how to use the
DelayQueue
:public class DelayQueueExample { public static void main(String[] args) { DelayQueue queue = new DelayQueue(); Delayed element1 = new DelayedElement(); queue.put(element1); Delayed element2 = queue.take(); } }The
DelayedElement
is an implementation of theDelayed
interface that I have created. It is not part of thejava.util.concurrent
package. You will have to create your own implementation of theDelayed
interface to use theDelayQueue
class.
LinkedBlockingQueue
The
LinkedBlockingQueue
class implements theBlockingQueue
interface. Read theBlockingQueue
text for more information about the interface.The
LinkedBlockingQueue
keeps the elements internally in a linked structure (linked nodes). This linked structure can optionally have an upper bound if desired. If no upper bound is specified,Integer.MAX_VALUE
is used as the upper bound.The
LinkedBlockingQueue
stores the elements internally in FIFO (First In, First Out) order. Thehead
of the queue is the element which has been in queue the longest time, and thetail
of the queue is the element which has been in the queue the shortest time.Here is how to instantiate and use a
LinkedBlockingQueue
:BlockingQueueunbounded = new LinkedBlockingQueue (); BlockingQueue bounded = new LinkedBlockingQueue (1024); bounded.put("Value"); String value = bounded.take();
PriorityBlockingQueue
The
PriorityBlockingQueue
class implements theBlockingQueue
interface. Read theBlockingQueue
text for more information about the interface.The
PriorityBlockingQueue
is an unbounded concurrent queue. It uses the same ordering rules as thejava.util.PriorityQueue
class. You cannot insert null into this queue.All elements inserted into the
PriorityBlockingQueue
must implement thejava.lang.Comparable
interface. The elements thus order themselves according to whatever priority you decide in yourComparable
implementation.Notice that the
PriorityBlockingQueue
does not enforce any specific behaviour for elements that have equal priority (compare() == 0).Also notice, that in case you obtain an
Iterator
from aPriorityBlockingQueue
, theIterator
does not guarantee to iterate the elements in priority order.Here is an example of how to use the
PriorityBlockingQueue
:BlockingQueue queue = new PriorityBlockingQueue(); //String implements java.lang.Comparable queue.put("Value"); String value = queue.take();
SynchronousQueue
The
SynchronousQueue
class implements theBlockingQueue
interface. Read theBlockingQueue
text for more information about the interface.The
SynchronousQueue
is a queue that can only contain a single element internally. A thread inseting an element into the queue is blocked until another thread takes that element from the queue. Likewise, if a thread tries to take an element and no element is currently present, that thread is blocked until a thread insert an element into the queue.Calling this class a queue is a bit of an overstatement. It's more of a rendesvouz point.
The
BlockingDeque
interface in thejava.util.concurrent
class represents a deque which is thread safe to put into, and take instances from. In this text I will show you how to use thisBlockingDeque
.The
BlockingDeque
class is aDeque
which blocks threads tring to insert or remove elements from the deque, in case it is either not possible to insert or remove elements from the deque.A
deque
is short for "Double Ended Queue". Thus, adeque
is a queue which you can insert and take elements from, from both ends.
BlockingDeque
BlockingDeque
BlockingDeque Usage
A
BlockingDeque
could be used if threads are both producing and consuming elements of the same queue. It could also just be used if the producting thread needs to insert at both ends of the queue, and the consuming thread needs to remove from both ends of the queue. Here is an illustration of that:
A BlockingDeque - threads can put and take from both ends of the deque. A thread will produce elements and insert them into either end of the queue. If the deque is currently full, the inserting thread will be blocked until a removing thread takes an element out of the deque. If the deque is currently empty, a removing thread will be blocked until an inserting thread inserts an element into the deque.
BlockingDeque methods
A
BlockingDeque
has 4 different sets of methods for inserting, removing and examining the elements in the deque. Each set of methods behaves differently in case the requested operation cannot be carried out immediately. Here is a table of the methods:
Throws Exception Special Value Blocks Times Out Insert addFirst(o)
offerFirst(o)
putFirst(o)
offerFirst(o, timeout, timeunit)
Remove removeFirst(o)
pollFirst(o)
takeFirst(o)
pollFirst(timeout, timeunit)
Examine getFirst(o)
peekFirst(o)
Throws Exception Special Value Blocks Times Out Insert addLast(o)
offerLast(o)
putLast(o)
offerLast(o, timeout, timeunit)
Remove removeLast(o)
pollLast(o)
takeLast(o)
pollLast(timeout, timeunit)
Examine getLast(o)
peekLast(o)
The 4 different sets of behaviour means this:
- Throws Exception:
If the attempted operation is not possible immediately, an exception is thrown.- Special Value:
If the attempted operation is not possible immediately, a special value is returned (often true / false).- Blocks:
If the attempted operation is not possible immedidately, the method call blocks until it is.- Times Out:
If the attempted operation is not possible immedidately, the method call blocks until it is, but waits no longer than the given timeout. Returns a special value telling whether the operation succeeded or not (typically true / false).BlockingDeque Extends BlockingQueue
The
BlockingDeque
interface extends theBlockingQueue
interface. That means that you can use aBlockingDeque
as aBlockingQueue
. If you do so, the various inserting methods will add the elements to the end of the deque, and the removing methods will remove the elements from the beginning of the deque. The inserting and removing methods of theBlockingQueue
interface, that is.Here is a table of what the methods of the
BlockingQueue
does in aBlockingDeque
implementation:
BlockingQueue BlockingDeque add() addLast() offer() x 2 offerLast() x 2 put() putLast() remove() removeFirst() poll() x 2 pollFirst() take() takeFirst() element() getFirst() peek() peekFirst() BlockingDeque Implementations
Since
BlockingDeque
is an interface, you need to use one of its many implementations to use it. Thejava.util.concurrent
package has the following implementations of theBlockingDeque
interface:
- LinkedBlockingDeque
BlockingDeque Code Example
Here is a small code example of how to use the
BlockingDeque
methods:BlockingDequedeque = new LinkedBlockingDeque (); deque.addFirst("1"); deque.addLast("2"); String two = deque.takeLast(); String one = deque.takeFirst(); LinkedBlockingDeque
The
LinkedBlockingDeque
class implements theBlockingDeque
interface. Read theBlockingDeque
text for more information about the interface.The word
Deque
comes from the term "Double Ended Queue". ADeque
is thus a queue where you can insert and remove elements from both ends of the queue.The
LinkedBlockingDeque
is aDeque
which will block if a thread attempts to take elements out of it while it is empty, regardless of what end the thread is attempting to take elements from.Here is how to instantiate and use a
LinkedBlockingDeque
:BlockingDequedeque = new LinkedBlockingDeque (); deque.addFirst("1"); deque.addLast("2"); String two = deque.takeLast(); String one = deque.takeFirst();
ConcurrentMap
java.util.concurrent.ConcurrentMap
The
java.util.concurrent.ConcurrentMap
interface represents a Map which is capable of handling concurrent access (puts and gets) to it.The
ConcurrentMap
has a few extra atomic methods in addition to the methods it inherits from its superinterface,java.util.Map
.ConcurrentMap Implementations
Since
ConcurrentMap
is an interface, you need to use one of its implementations in order to use it. Thejava.util.concurrent
package contains the following implementations of theConcurrentMap
interface:
- ConcurrentHashMap
ConcurrentHashMap
The
ConcurrentHashMap
is very similar to thejava.util.HashTable
class, except thatConcurrentHashMap
offers better concurrency thanHashTable
does.ConcurrentHashMap
does not lock theMap
while you are reading from it. Additionally,ConcurrentHashMap
does not lock the entireMap
when writing to it. It only locks the part of theMap
that is being written to, internally.Another difference is that
ConcurrentHashMap
does not throwConcurrentModificationException
if theConcurrentHashMap
is changed while being iterated. TheIterator
is not designed to be used by more than one thread though.Checkout the official JavaDoc for more details about
ConcurrentMap
andConcurrentHashMap
.ConcurrentMap Example
Here is an example of how to use the
ConcurrentMap
interface. The example uses aConcurrentHashMap
implementation:ConcurrentMap concurrentMap = new ConcurrentHashMap(); concurrentMap.put("key", "value"); Object value = concurrentMap.get("key");
ConcurrentNavigableMap
The
java.util.concurrent.ConcurrentNavigableMap
class is a java.util.NavigableMap with support for concurrent access, and which has concurrent access enabled for its submaps. The "submaps" are the maps returned by various methods likeheadMap()
,subMap()
andtailMap()
.Rather than re-explain all methods found in the
NavigableMap
I will just look at the methods added byConcurrentNavigableMap
.headMap()
The
headMap(T toKey)
method returns a view of the map containing the keys which are strictly less than the given key.If you make changes to the original map, these changes are reflected in the head map.
Here is an example illustrating 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");The
headMap
will point to aConcurrentNavigableMap
which only contains the key"1"
, since only this key is strictly less than"2"
.See the JavaDoc for more specific details of how this method works, and how its overloaded versions work.
tailMap()
The
tailMap(T fromKey)
method returns a view of the map containing the keys which are greater than or equal to the givenfromKey
.If you make changes to the original map, these changes are reflected in the tail map.
Here is an example illustrating 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");The
tailMap
will contain the keys"2"
and"3"
because these two keys are greather than or equal to the given key,"2"
.See the JavaDoc for more specific details of how this method works, and how its overloaded versions work.
subMap()
The
subMap()
method returns a view of the original map which contains all keys from (including), to (excluding) two keys given as parameters to the method. Here 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");The returned submap contains only the key
"2"
, because only this key is greater than or equal to"2"
, and smaller than"3"
.More Methods
The
ConcurrentNavigableMap
interface contains a few more methods that might be of use. For instance:
- descendingKeySet()
- descendingMap()
- navigableKeySet()
See the official JavaDoc for more information on these methods.
CountDownLatch
A
java.util.concurrent.CountDownLatch
is a concurrency construct that allows one or more threads to wait for a given set of operations to complete.A
CountDownLatch
is initialized with a given count. This count is decremented by calls to thecountDown()
method. Threads waiting for this count to reach zero can call one of theawait()
methods. Callingawait()
blocks the thread until the count reaches zero.Below is a simple example. After the
Decrementer
has calledcountDown()
3 times on theCountDownLatch
, the waitingWaiter
is released from theawait()
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() { 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() { try { Thread.sleep(1000); this.latch.countDown(); Thread.sleep(1000); this.latch.countDown(); Thread.sleep(1000); this.latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }
CyclicBarrier
The
java.util.concurrent.CyclicBarrier
class is a synchronization mechanism that can synchronize threads progressing through some algorithm. In other words, it is a barrier that all threads must wait at, until all threads reach it, before any of the threads can continue. Here is a diagram illustrating that:
Two threads waiting for each other at CyclicBarriers. The threads wait for each other by calling the
await()
method on theCyclicBarrier
. Once N threads are waiting at theCyclicBarrier
, all threads are released and can continue running.Creating a CyclicBarrier
When you create a
CyclicBarrier
you specify how many threads are to wait at it, before releasing them. Here is how you create aCyclicBarrier
:CyclicBarrier barrier = new CyclicBarrier(2);Waiting at a CyclicBarrier
Here is how a thread waits at a
CyclicBarrier
:barrier.await();You can also specify a timeout for the waiting thread. When the timeout has passed the thread is also released, even if not all N threads are waiting at the
CyclicBarrier
. Here is how you specify a timeout:barrier.await(10, TimeUnit.SECONDS);The waiting threads waits at the
CyclicBarrier
until either:
- The last thread arrives (calls await() )
- The thread is interrupted by another thread (another thread calls its interrupt() method)
- Another waiting thread is interrupted
- Another waiting thread times out while waiting at the
CyclicBarrier
- The
CyclicBarrier.reset()
method is called by some external thread.CyclicBarrier Action
The
CyclicBarrier
supports a barrier action, which is aRunnable
that is executed once the last thread arrives. You pass theRunnable
barrier action to theCyclicBarrier
in its constructor, like this:Runnable barrierAction = ... ; CyclicBarrier barrier = new CyclicBarrier(2, barrierAction);CyclicBarrier Example
Here is a code example that shows you how to use a
CyclicBarrier
:Runnable barrier1Action = new Runnable() { public void run() { System.out.println("BarrierAction 1 executed "); } }; Runnable barrier2Action = new Runnable() { public void run() { 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();Here is the
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() { 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(); } } }Here is the console output for an execution of the above code. Note that the sequence in which the threads gets to write to the console may vary from execution to execution. Sometimes
Thread-0
prints first, sometimesThread-1
prints first etc.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!
Exchanger
The
java.util.concurrent.Exchanger
class represents a kind of rendezvous point where two threads can exchange objects. Here is an illustration of this mechanism:
Two threads exchanging objects via an Exchanger. Exchanging objects is done via one of the two
exchange()
methods. Here is an example:Exchanger exchanger = new Exchanger(); ExchangerRunnable exchangerRunnable1 = new ExchangerRunnable(exchanger, "A"); ExchangerRunnable exchangerRunnable2 = new ExchangerRunnable(exchanger, "B"); new Thread(exchangerRunnable1).start(); new Thread(exchangerRunnable2).start();Here is the
ExchangerRunnable
code:public 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() { 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(); } } }This example prints out this:
Thread-0 exchanged A for B Thread-1 exchanged B for A
Semaphore
The
java.util.concurrent.Semaphore
class is a counting semaphore. That means that it has two main methods:
- acquire()
- release()
The counting semaphore is initialized with a given number of "permits". For each call to
acquire()
a permit is taken by the calling thread. For each call torelease()
a permit is returned to the semaphore. Thus, at most N threads can pass theacquire()
method without anyrelease()
calls, where N is the number of permits the semaphore was initialized with. The permits are just a simple counter. Nothing fancy here.Semaphore Usage
As semaphore typically has two uses:
- To guard a critical section against entry by more than N threads at a time.
- To send signals between two threads.
Guarding Critical Sections
If you use a semaphore to guard a critical section, the thread trying to enter the critical section will typically first try to acquire a permit, enter the critical section, and then release the permit again after. Like this:
Semaphore semaphore = new Semaphore(1); //critical section semaphore.acquire(); ... semaphore.release();Sending Signals Between Threads
If you use a semaphore to send signals between threads, then you would typically have one thread call the
acquire()
method, and the other thread to call therelease()
method.If no permits are available, the
acquire()
call will block until a permit is released by another thread. Similarly, arelease()
calls is blocked if no more permits can be released into this semaphore.Thus it is possible to coordinate threads. For instance, if acquire was called after Thread 1 had inserted an object in a shared list, and Thread 2 had called
release()
just before taking an object from that list, you had essentially created a blocking queue. The number of permits available in the semaphore would correspond to the maximum number of elements the blocking queue could hold.Fairness
No guarantees are made about fairness of the threads acquiring permits from the
Semaphore
. That is, there is no guarantee that the first thread to callacquire()
is also the first thread to obtain a permit. If the first thread is blocked waiting for a permit, then a second thread checking for a permit just as a permit is released, may actually obtain the permit ahead of thread 1.If you want to enforce fairness, the
Semaphore
class has a constructor that takes a boolean telling if the semaphore should enforce fairness. Enforcing fairness comes at a performance / concurrency penalty, so don't enable it unless you need it.Here is how to create a
Semaphore
in fair mode:Semaphore semaphore = new Semaphore(1, true);More Methods
The
java.util.concurrent.Semaphore
class has lots more methods. For instance:
availablePermits()
acquireUninterruptibly()
drainPermits()
hasQueuedThreads()
getQueuedThreads()
tryAcquire()
etc.
Check out the JavaDoc for more details on these methods.
ExecutorService
Thejava.util.concurrent.ExecutorService
interface represents an asynchronous execution mechanism which is capable of executing tasks in the background. AnExecutorService
is thus very similar to a thread pool . In fact, the implementation ofExecutorService
present in thejava.util.concurrent
package is a thread pool implementation.
ExecutorService Example
Here is a simple Java
ExectorService
example:ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); executorService.shutdown();First an
ExecutorService
is created using thenewFixedThreadPool()
factory method. This creates a thread pool with 10 threads executing tasks.Second, an anonymous implementation of the
Runnable
interface is passed to theexecute()
method. This causes theRunnable
to be executed by one of the threads in theExecutorService
.Task Delegation
Here is a diagram illustrating a thread delegating a task to an
ExecutorService
for asynchronous execution:
A thread delegating a task to an ExecutorService for asynchronous execution. Once the thread has delegated the task to the
ExecutorService
, the thread continues its own execution independent of the execution of that task.ExecutorService Implementations
Since
ExecutorService
is an interface, you need to its implementations in order to make any use of it. TheExecutorService
has the following implementation in thejava.util.concurrent
package:
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
Creating an ExecutorService
How you create an
ExecutorService
depends on the implementation you use. However, you can use theExecutors
factory class to createExecutorService
instances too. Here are a few examples of creating anExecutorService
:ExecutorService executorService1 = Executors.newSingleThreadExecutor(); ExecutorService executorService2 = Executors.newFixedThreadPool(10); ExecutorService executorService3 = Executors.newScheduledThreadPool(10);ExecutorService Usage
There are a few different ways to delegate tasks for execution to an
ExecutorService
:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)
I will take a look at each of these methods in the following sections.
execute(Runnable)
The
execute(Runnable)
method takes ajava.lang.Runnable
object, and executes it asynchronously. Here is an example of executing aRunnable
with anExecutorService
:ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); executorService.shutdown();There is no way of obtaining the result of the executed
Runnable
, if necessary. You will have to use aCallable
for that (explained in the following sections).
submit(Runnable)
The
submit(Runnable)
method also takes aRunnable
implementation, but returns aFuture
object. ThisFuture
object can be used to check if theRunnable
as finished executing.Here is a
ExecutorService
submit()
example:Future future = executorService.submit(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); future.get(); //returns null if the task has finished correctly.
submit(Callable)
The
submit(Callable)
method is similar to thesubmit(Runnable)
method except for the type of parameter it takes. TheCallable
instance is very similar to aRunnable
except that itscall()
method can return a result. TheRunnable.run()
method cannot return a result.The
Callable
's result can be obtained via theFuture
object returned by thesubmit(Callable)
method. Here is anExecutorService
Callable
example:Future future = executorService.submit(new Callable(){ public Object call() throws Exception { System.out.println("Asynchronous Callable"); return "Callable Result"; } }); System.out.println("future.get() = " + future.get());The above code example will output this:
Asynchronous Callable future.get() = Callable Result
invokeAny()
The
invokeAny()
method takes a collection ofCallable
objects, or subinterfaces ofCallable
. Invoking this method does not return aFuture
, but returns the result of one of theCallable
objects. You have no guarantee about which of theCallable
's results you get. Just one of the ones that finish.If one of the tasks complete (or throws an exception), the rest of the
Callable
's are cancelled.Here is a code example:
ExecutorService executorService = Executors.newSingleThreadExecutor(); Set> callables = new HashSet >(); callables.add(new Callable () { public String call() throws Exception { return "Task 1"; } }); callables.add(new Callable () { public String call() throws Exception { return "Task 2"; } }); callables.add(new Callable () { public String call() throws Exception { return "Task 3"; } }); String result = executorService.invokeAny(callables); System.out.println("result = " + result); executorService.shutdown(); This code example will print out the object returned by one of the
Callable
's in the given collection. I have tried running it a few times, and the result changes. Sometimes it is "Task 1", sometimes "Task 2" etc.
invokeAll()
The
invokeAll()
method invokes all of theCallable
objects you pass to it in the collection passed as parameter. TheinvokeAll()
returns a list ofFuture
objects via which you can obtain the results of the executions of eachCallable
.Keep in mind that a task might finish due to an exception, so it may not have "succeeded". There is no way on a
Future
to tell the difference.Here is a code example:
ExecutorService executorService = Executors.newSingleThreadExecutor(); Set> callables = new HashSet >(); callables.add(new Callable () { public String call() throws Exception { return "Task 1"; } }); callables.add(new Callable () { public String call() throws Exception { return "Task 2"; } }); callables.add(new Callable () { public String call() throws Exception { return "Task 3"; } }); List > futures = executorService.invokeAll(callables); for(Future future : futures){ System.out.println("future.get = " + future.get()); } executorService.shutdown(); ExecutorService Shutdown
When you are done using the
ExecutorService
you should shut it down, so the threads do not keep running.For instance, if your application is started via a
main()
method and your main thread exits your application, the application will keep running if you have an activeExexutorService
in your application. The active threads inside thisExecutorService
prevents the JVM from shutting down.To terminate the threads inside the
ExecutorService
you call itsshutdown()
method. TheExecutorService
will not shut down immediately, but it will no longer accept new tasks, and once all threads have finished current tasks, theExecutorService
shuts down. All tasks submitted to theExecutorService
beforeshutdown()
is called, are executed.If you want to shut down the
ExecutorService
immediately, you can call theshutdownNow()
method. This will attempt to stop all executing tasks right away, and skips all submitted but non-processed tasks. There are no guarantees given about the executing tasks. Perhaps they stop, perhaps the execute until the end. It is a best effort attempt.
ThreadPoolExecutor
The
java.util.concurrent.ThreadPoolExecutor
is an implementation of theExecutorService
interface. TheThreadPoolExecutor
executes the given task (Callable
orRunnable
) using one of its internally pooled threads.The thread pool contained inside the
ThreadPoolExecutor
can contain a varying amount of threads. The number of threads in the pool is determined by these variables:
corePoolSize
maximumPoolSize
If less than
corePoolSize
threads are created in the the thread pool when a task is delegated to the thread pool, then a new thread is created, even if idle threads exist in the pool.If the internal queue of tasks is full, and
corePoolSize
threads or more are running, but less thanmaximumPoolSize
threads are running, then a new thread is created to execute the task.Here is a diagram illustrating the
ThreadPoolExecutor
principles:
A ThreadPoolExecutor
Creating a ThreadPoolExecutor
The
ThreadPoolExecutor
has several constructors available. For instance:int corePoolSize = 5; int maxPoolSize = 10; long keepAliveTime = 5000; ExecutorService threadPoolExecutor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue() ); However, unless you need to specify all these parameters explicitly for your
ThreadPoolExecutor
, it is often easier to use one of the factory methods in thejava.util.concurrent.Executors
class, as shown in the ExecutorServicetext.
ScheduledExecutorService
The
java.util.concurrent.ScheduledExecutorService
is anExecutorService
which can schedule tasks to run after a delay, or to execute repeatedly with a fixed interval of time in between each execution. Tasks are executed asynchronously by a worker thread, and not by the thread handing the task to theScheduledExecutorService
.ScheduledExecutorService Example
Here is a simple
ScheduledExecutorService
example:ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(new Callable() { public Object call() throws Exception { System.out.println("Executed!"); return "Called!"; } }, 5, TimeUnit.SECONDS);First a
ScheduledExecutorService
is created with 5 threads in. Then an anonymous implementation of theCallable
interface is created and passed to theschedule()
method. The two last parameters specify that theCallable
should be executed after 5 seconds.ScheduledExecutorService Implementations
Since
ScheduledExecutorService
is an interface, you will have to use its implementation in thejava.util.concurrent
package, in order to use it.ScheduledExecutorService
as the following implementation:
- ScheduledThreadPoolExecutor
Creating a ScheduledExecutorService
How you create an
ScheduledExecutorService
depends on the implementation you use. However, you can use theExecutors
factory class to createScheduledExecutorService
instances too. Here is an example:ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);ScheduledExecutorService Usage
Once you have created a
ScheduledExecutorService
you use it by calling one of its 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)
I will briefly cover each of these methods below.
schedule (Callable task, long delay, TimeUnit timeunit)
This method schedules the given
Callable
for execution after the given delay.The method returns a
ScheduledFuture
which you can use to either cancel the task before it has started executing, or obtain the result once it is executed.Here is an example:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(new Callable() { public Object call() throws Exception { System.out.println("Executed!"); return "Called!"; } }, 5, TimeUnit.SECONDS); System.out.println("result = " + scheduledFuture.get()); scheduledExecutorService.shutdown();This example outputs:
Executed! result = Called!schedule (Runnable task, long delay, TimeUnit timeunit)
This method works like the method version taking a
Callable
as parameter, except aRunnable
cannot return a value, so theScheduledFuture.get()
method returns null when the task is finished.scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
This method schedules a task to be executed periodically. The task is executed the first time after the
initialDelay
, and then recurringly every time theperiod
expires.If any execution of the given task throws an exception, the task is no longer executed. If no exceptions are thrown, the task will continue to be executed until the
ScheduledExecutorService
is shut down.If a task takes longer to execute than the period between its scheduled executions, the next execution will start after the current execution finishes. The scheduled task will not be executed by more than one thread at a time.
scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)
This method works very much like
scheduleAtFixedRate()
except that theperiod
is interpreted differently.In the
scheduleAtFixedRate()
method theperiod
is interpreted as a delay between the start of the previous execution, until the start of the next execution.In this method, however, the
period
is interpreted as the delay between the end of the previous execution, until the start of the next. The delay is thus between finished executions, not between the beginning of executions.ScheduledExecutorService Shutdown
Just like an
ExecutorService
, theScheduledExecutorService
needs to be shut down when you are finished using it. If not, it will keep the JVM running, even when all other threads have been shut down.You shut down a
ScheduledExecutorService
using theshutdown()
orshutdownNow()
methods which are inherited from theExecutorService
interface. See the ExecutorService Shutdown section for more information.
Lock
A
java.util.concurrent.locks.Lock
is a thread synchronization mechanism just likesynchronized
blocks. ALock
is, however, more flexible and more sophisticated than a synchronized block.By the way, in my Java Concurrency tutorial I have described how to implement your own locks, in case you are interested (or need it). See my text on Locks for more details.
Java Lock Example
Since
Lock
is an interface, you need to use one of its implementations to use aLock
in your applications. Here is a simple usage example:Lock lock = new ReentrantLock(); lock.lock(); //critical section lock.unlock();First a
Lock
is created. Then it'slock()
method is called. Now theLock
instance is locked. Any other thread callinglock()
will be blocked until the thread that locked the lock callsunlock()
. Finallyunlock()
is called, and theLock
is now unlocked so other threads can lock it.Java Lock Implementations
The
java.util.concurrent.locks
package has the following implementations of theLock
interface:
- ReentrantLock
Main Differences Between Locks and Synchronized Blocks
The main differences between a
Lock
and a synchronized block are:
- A synchronized block makes no guarantees about the sequence in which threads waiting to entering it are granted access.
- You cannot pass any parameters to the entry of a synchronized block. Thus, having a timeout trying to get access to a synchronized block is not possible.
- The synchronized block must be fully contained within a single method. A
Lock
can have it's calls tolock()
andunlock()
in separate methods.Lock Methods
The
Lock
interface has the following primary methods:
- lock()
- lockInterruptibly()
- tryLock()
- tryLock(long timeout, TimeUnit timeUnit)
- unlock()
The
lock()
method locks theLock
instance if possible. If theLock
instance is already locked, the thread callinglock()
is blocked until theLock
is unlocked.The
lockInterruptibly()
method locks theLock
unless the thread calling the method has been interrupted. Additionally, if a thread is blocked waiting to lock theLock
via this method, and it is interrupted, it exits this method calls.The
tryLock()
method attempts to lock theLock
instance immediately. It returnstrue
if the locking succeeds, false ifLock
is already locked. This method never blocks.The
tryLock(long timeout, TimeUnit timeUnit)
works like thetryLock()
method, except it waits up the given timeout before giving up trying to lock theLock
.The
unlock()
method unlocks theLock
instance. Typically, aLock
implementation will only allow the thread that has locked theLock
to call this method. Other threads calling this method may result in an unchecked exception (RuntimeException
).
ReadWriteLock
A
java.util.concurrent.locks.ReadWriteLock
is an advanced thread lock mechanism. It allows multiple threads to read a certain resource, but only one to write it, at a time.The idea is, that multiple threads can read from a shared resource without causing concurrency errors. The concurrency errors first occur when reads and writes to a shared resource occur concurrently, or if multiple writes take place concurrently.
In this text I only cover Java's built-in
ReadWriteLock
. If you want to read more about the theory behind the implemenation of a ReadWriteLock, you can read it in my text on Read Write Locks in my Java Concurrency tutorial.ReadWriteLock Locking Rules
The rules by which a thread is allowed to lock the
ReadWriteLock
either for reading or writing the guarded resource, are as follows:
Read Lock If no threads have locked the ReadWriteLock
for writing,
and no thread have requested a write lock (but not yet obtained it).
Thus, multiple threads can lock the lock for reading.Write Lock If no threads are reading or writing.
Thus, only one thread at a time can lock the lock for writing.ReadWriteLock Implementations
ReadWriteLock
is an interface. Thus, to use aReadWriteLock
The
java.util.concurrent.locks
package contains the followingReadWriteLock
implementation:
- ReentrantReadWriteLock
ReadWriteLock Code Example
Here is a simple code example that shows how to create a
ReadWriteLock
and how to lock it for reading and writing: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();Notice how the
ReadWriteLock
actually internally keeps twoLock
instances. One guarding read access, and one guarding write access.