Notes on Java Concurrency
Here I collect my notes about the topic of Java concurrency. This is probably not a complete or useful article for anyone except for me. But you can still check out the reference list to find more complete sources.
Thread.class
can be used to run a code block implementing in an Runnable
interface.
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("The thread name is " + threadName);
};
task.run();
Thread thread = new Thread(task);
thread.start();
When you have more threads, the Executor can be used to manage them. For example, you can have a pool of threads. When there is a task, one of these threads can execute it, if all the threads are busy, then the task waits in the queue.
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("The thread name is " + threadName);
};
ExecutorService executor = Executor.newFixedThreadPool(3);
executor.submit(task);
executor.submit(task);
executor.submit(task);
executor.submit(task);
executor.shutdown(); // shut down after all the tasks have compleeted.
Callable & Future
If there is a need to return a result from a thread task, then use Callable
interface instead of Runnable. The return value is represented via Future
Callable<Double> task = () -> {
return Math.random();
};
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<Double> result = executor.submit(task);
Double d = result.get(2, TimeUnit.SECONDS); // wait for 2 seconds at most
System.out.println(d);
Synchronization and locks
Intuitively the code below is expected to increase val
to 50, as many times as the increment method is invoked. But in reality, val
may not be 50 because of race condition. That is, while one thread increasing it to 5, for example, another thread may be doing exactly the same work.
static int val;
static void increment() {
val = val + 1;
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 50; i++) {
executor.submit(() -> {
increment();
});
}
executor.shutdown();
System.out.println(val);
}
If increment is only invoked executed by a single thread at a time, then this race condition will not occur. To do so, we can wrap the code pieces with synchronized so that only one thread can enter within it.
int val;
void increment() {
synchronized (this) {
val = val + 1;
}
}
Or locks can be used as follows:
static ReentrantLock lock = new ReentrantLock();
static void increment() {
lock.lock();
val = val + 1;
lock.unlock();
}
Semaphore
Limit the number of threads acquiring permits to run a code block.
Semaphore semaphore = new Semaphore(3);
void increment() {
final boolean permit = semaphore.tryAcquire(1, TimeUnit.MILLISECONDS);
if (permit) {
Thread.sleep(1000);
synchronized (this) {
val = val + 1;
}
semaphore.release();
}
}
Topics
- Threads
- Runnable vs Callable, Future, CompletableFuture
- Executors
- Scheduled Executors
- ForkJoinPool
- Synchronized and locks
- monitor lock or intrinsic lock,
- ReentrantLock, ReadWriteLock, StampedLock
- Atomic operations, compare-and-swap (CAS)
- LongAdder, LongAccumulator
- ConcurrenctMap
- Fork-join
References
- https://winterbe.com/posts/2015/04/07/java8-concurrency-tutorial-thread-executor-examples/
- https://www.vogella.com/tutorials/JavaConcurrency/article.html
- https://developer.ibm.com/articles/j-5things15/?mhsrc=ibmsearch_a&mhq=%20non%20blocking
- https://developer.ibm.com/articles/j-5things4/
- https://developer.ibm.com/articles/j-5things5/