This article is an unreviewed draft and may contain incorrect information.
Fundamentals
Basic Info
- one Java
Thread is one OS Thread
Methods
join()- better name would be:
waitForCompletion() - then actual invocation:
parent.waitForCompletion(threadOne) → parent joins threadOne execution waiting for it to finish
wait()- give lock back away and wait for
notify() call
notify()- notifies the waiting one to wake up
Producer Consumer
- Producer
produces >= 1 && Consumer consumes >=1 && common_buffer
Simple pseudocode:
1
2
3
4
5
6
| produce:
if buffer < max:
buffer.add()
lock.notify()
else
lock.wait()
|
1
2
3
4
5
6
| consume:
if buffer > 0:
buffer.take()
lock.notify()
else
lock.wait()
|
volatile
volatile - makes CPU read/write from common memory (like L3 Cache) and not is own registry
ExecutorService
SingleThreadExecutor
1
2
3
4
| try(ExecutorService service =
Executors.newSingleThreadExecutor()) {
// service.execute(Runnable runnable)
}
|
FixedThreadPool
1
2
3
4
| try(ExecutorService service =
Executors.newFixedThreadPool(int nThreads)) {
// service.execute(Runnable runnable)
}
|
CachedThreadPool
some kind of “autoscaling” Pool (60s idle → terminate Thread)
1
2
3
4
| try(ExecutorService service =
Executors.newCachedThreadPool()) {
// service.execute(Runnable runnable)
}
|
Scheduled execution
Turn on
1
2
3
4
5
6
7
8
| try(ExecutorService service = Executors.newSingleThreadExecutor()) {
service.scheduleAtFixedRate(
Runnable task,
long firstDelay,
long period,
TimeUnit timeUnit
);
}
|
Shut down
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| try {
// true: executor terminated / false: timeout elapsed
boolean succeeded =
service.awaitTermination(
long timeout,
TimeUnit timeUnit
);
if (!succeeded) {
// force shutdown
service.shutdownNow(); // shutdown() would be graceful
} catch (InterruptedException e) {
service.shutdownNow();
}
}
|
Callable and Future
future.get()- blocking operation, waiting until result arrives
throws ExecutionException, Interrupted Exception
future.get(long timeout, TimeUnit timeUnit)- blocking operation, waiting until timeout
throws TimeoutException (apart from InterruptedException, ExecutionException)
future.cancel(boolean mayInterruptIfRunning)- attempts to cancel task, may also try to interrupt
future.isCancelled()future.isDone()
1
2
3
4
5
6
7
8
9
| Future<Response> response =
service.submit(
new Callable<Response>() {
@Override
public Response call() throws Exception {
return someRequest.invoke();
}
}
);
|
Synchronized Collections
Manual
1
2
3
4
| List<Integer> list =
Collections.synchronizedList(
new ArrayList()
);
|
- coarse-grained locking (single
Lock for everything) - limited functionality (no additional methods for locking)
- no fail-fast Iterators (e.g. not throwing
InterruptedException) - performance overhead (lock acquisition/release overhead)
BlockingQueue
General behavior:
- taking from empty →
wait - adding to full →
wait
Methods:
put(E e) - put or wait if fulltake(): E - take or wait if emptyoffer(E e): boolean - true: element added / false: queue fullpoll(): E? - take from head / null: if emptypeek(): E? - get item without taking / null if empty
Implementations:
BlockingDeque - double-ended queue (deck)TransferQueue - allows Producer to directly transfer to waiting ConsumerArrayBlockingQueue - bounded queue, backed by ArrayListLinkedBlockingQueue - (un)bounded queue, backed by LinkedListPriorityBlockingQueue - orders items by ComparatorDelayQueue - only expired delay items can te takenSynchronousQueue - zero capacity, only direct transfers
ConcurrentMap
Implementations:
ConcurrentHashMapConcurrentSkipListMapConcurrentLinkedHashMapConcurrentNavigableMap
CopyOnWriteArray
- writers won’t interfere with readers
- some like
Git branching
1
2
3
4
5
6
7
| List<Integer> list =
new CopyOnWriteArrayList<>();
// Thread1 can read
sout(list);
// Thread2 can write
list.set(index, value);
|
Atomic Variables
read-modify-write cycle
count++ is actually:
- Load count value
- Increment loaded value
- Set value to variable
Basic Operations
get()set()compareAndSet(expected, updated) - if (expected) then → updategetAndIncrement() / incrementAndGet()getAndDecrement() / decrementAndGet()
Implementations
AtomicIntegerAtomicBooleanAtomicLong
Mutexes
CountDownLatch
CountDownLatch is single use, cannot be reused
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| CountDownLatch latch =
new CountDownLatch(int count);
// e.g. for count = 3:
Runnable runnable = (latch) -> {
Thread.sleep(random);
// decrease latch
latch.countDown();
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
// blocks and awaits for all 3 `countDown()` calls
latch.await();
|
CyclicBarrier
E.g. used for multiple checkpoints during a play when we have to wait for all players to reach given point to proceed
1
2
3
4
5
6
7
| CyclicBarrier barrier =
new CyclicBarrier(
int cycle,
Runnable action // what to do when releasing
)
barrier.await();
|
Exchanger
- synchronization point at which
Threads can pair and swap elements within concurrent environment - e.g. used if you create pipeline for adjacent steps
1
2
3
4
5
6
7
8
9
| Exchanger<String> exchanger =
new Exchanger<>();
// Thread1: calls
String dataFromThread2 =
exchanger.exchange("DataToFlow: Thread1 → Thread2"); // waiting for another `Thread`
// Thread2: calls
String dataFromThread1 =
exchanger.exchange("DataToFlow: Thread2 → Thread1"); // exchange takes place in this step
|
Condition
Condition can be e.g. Queue being full → await()- when
Condition is met, there goes signal → `signal()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| Lock lock = new ReentrantLock();
// Conditions are not programmable, we have to use them properly
// so naming is very important!
Condition bufferNotFull = lock.newCondition();
Condition bufferNotEmpty = lock.newCondition();
// Producer:
lock.lock();
try {
while(buffer.size() == MAX_SIZE) {
bufferNotFull.await(); // wait for condition to be met
}
buffer.offer(item);
bufferNotEmpty.signal(); // signal that condition is now met
} finally {
lock.unlock(); // don't forget to unlock
}
// Consumer:
lock.lock();
try {
while(buffer.size() == 0) {
bufferNotEmpty.await(); // wait until buffer is not empty
}
buffer.poll();
bufferNotFull.signal() // let them know buffer is not full anymore
} finally {
lock.unlock(); // don't forget
}
|
ReentrantLock
ReentrantLock enables the same Thread to lock() multiple times without preceding unlock()- It has count how many times lock has been acquired by a given
Thread - Release takes place when counter reaches 0
- Fairness makes
Threads waiting most time higher in the Priority Queue - Without Fairness result is non-deterministic
Methods
getHoldCount(): int - count of current ThreadtryLock(): boolean - true if successfully acquired / false if not acquiredtryLock(timeout, timeUnit): boolean - similar with timeoutisHeldByCurrentThread(): booleangetQueueLength()newCondition()
Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| Lock lock =
new ReentrantLock(boolean fair);
methodA() {
lock.lock();
try {
value++;
methodB();
} finally {
lock.unlock();
}
}
methodB() {
lock.lock();
try {
value--;
} finally {
lock.unlock();
}
}
|
ReadWriteLock
- Used when resource is read heavy
- Many
Threads can read - Only one
Thread can write - It contains two separate
Locks - one for Readers, one for Writer - Only one of these
Locks can be active at a given time Threads are in Priority Queue and writers won’t be starved
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| ReadWriteLock lock =
new ReentrantReadWriteLock();
writeValue() {
lock.writeLock.lock();
try {
value++;
} finally {
lock.writeLock().unlock();
}
}
getValue() {
lock.readLock.lock();
try {
sout(value);
} finally {
lock.readLock.unlock();
}
}
|
Semaphore
acquire() / acquire(permits) - acquires permit(s)release() / release(permits) - release permit(s)tryAcquire()tryAcquire(timeout)availablePermits()
1
2
3
4
5
6
| Semaphore semaphore =
new Semaphore(int permits, boolean fair);
semaphore.acquire();
// ...
semaphore.release();
|
Deadlocks
Deadlock is lock release dependency cycle
jps -l - list processes running Javakill -3 <PID> - kill with Thread Dump
1
2
3
4
5
6
7
8
| ThreadMXBean mxBean =
ManagementFactory.getThreadMXBean();
long[] threadIds =
mxBean.findDeadlockedThreads();
ThreadInfo[] threadInfo =
mxBean.getThreadInfo(threadIds);
|
How to prevent Deadlocks
- Use Timeouts
- Take care of Global Ordering of the Locks
- e.g. always Lock in ascending order (LockA, LockB, LockC)
- Avoid nesting Locks
- Use Thread-Safe alternatives
ForkJoinPool
- similar to
ExecutorService ForkJoin can have subtasks- work stealing
- utilization of multi-core processors
- simplified parallelism
- efficient work stealing algorithms