Java - Concurrency

Updated: 2019-01-27

Most implementations of the Java virtual machine run as a single process. When we talk about "concurrency" in Java, it is mostly multi-thread instead of multi-process.

Keyword: synchronized

Synchronized on "intrinsic lock"("intrinsic lock" is implied by each use of synchronized keyword).

  • intrinsic lock = monitor lock
  • Object monitor methods: wait, notify and notifyAll

synchronized vs volatile vs Atomic

Two factors to consider:

  • mutual exclusion: no two concurrent processes/threads are in their critical section at the same time
  • inter-thread communication: data modified by one thread should be visible to other threads

synchronized:

  • mutual exclusion: to prevent an object from being observed in an inconsistent state while it’s being modified by another thread
  • reliable communication(visibility): guarantee that a value written by one thread will be visible to another.(defined in Java Memory Model)

volatile

  • no mutual exclusion
  • guarantees that any thread that reads the field will see the most recently written value

Atomic Types

  • mutual exclusion
  • no guarantee on visibility

Note: synchronization has no effect unless both read and write operations are synchronized.

happen-before

happens-before relationship: simply a guarantee that memory writes by one specific statement are visible to another specific statement.

The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation.

The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships.

https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility

Usage

Two types of intrinsic lock:

  • instance lock: attached to an object, only blocks other threads from invoking a synchronized instance method, not static synchronized method or methods without synchronized keyword.
  • static lock: attached to a class, only blocks other threads from invoking a static synchronized method, not a synchronized instance method or methods without synchronized keyword.

Two ways to use synchronized keyword:

  • synchronized method
  • synchronized statements: Unlike synchronized methods, synchronized statements must specify the object that provides the intrinsic lock

These two are equivalent:

Synchronized methods:

public synchronized void blah() {
  // ...
}

Synchronized statement: (synchronized (this) acquires the instance lock)

public void blah() {
  synchronized (this) {
    // ...
  }
}

To acquire a static in a synchronized statement(not in method header):

synchronized(Foo.class)
// or
synchronized(this.getClass())

Explicit Lock

Besides, the intrinsic locks, Java also provides explicit locks:

  • the interface: Lock
  • implementations: ReentrantLock, ReentrantReadWriteLock

The benefits:

  • support wait/notify, through their associated Condition objects
  • able to back out of an attempt to acquire a lock: tryLock method and lockInterruptibly method.

Where a Lock replaces the use of synchronized methods and statements, a Condition replaces the use of the Object monitor methods.

Java Runnable vs Callable

  • Runnable: does not return a result and cannot throw a checked exception.
  • Callable: return a result

JAVA Thread Scheduling

  • The JVM schedules using a preemptive, priority based scheduling algorithm.
  • All Java threads have a priority and the thread with he highest priority is scheduled to run by the JVM.
  • In case two threads have the same priority a FIFO ordering is followed.

A different thread is invoked to run in case one of the following events occur:

  1. The currently running thread exits the Runnable state ie either blocks or terminates.
  2. A thread with a higher priority than the thread currently running enters the Runnable state. The lower priority thread is preempted and the higher priority thread is scheduled to run.

Time Slicing is dependent on the implementation.

A thread can voluntarily yield control through the yield() method. Whenever a thread yields control of the CPU another thread of the same priority is scheduled to run. A thread voluntarily yielding control of the CPU is called Cooperative Multitasking.

Thread Priorities

JVM selects to run a Runnable thread with the highest priority.

All Java threads have a priority in the range 1-10.

Top priority is 10, lowest priority is 1.Normal

priority ie. priority by default is 5.

  • Thread.MIN_PRIORITY - minimum thread priority
  • Thread.MAX_PRIORITY - maximum thread priority
  • Thread.NORM_PRIORITY - normal thread priority

Whenever a new Java thread is created it has the same priority as the thread which created it.

Thread priority can be changed by the setpriority() method.

Fork/Join Framework

Official Doc

the idea is :

if (my portion of the work is small enough)
  do the work directly
else
  split my work into two pieces
  invoke the two pieces and wait for the results

Used in JDK:

  • java.util.Arrays Arrays.parallelSort()
  • java.util.streams

Fork-Join breaks the task at hand into mini-tasks until the mini-task is simple enough that it can be solved without further breakups.

Two key classes: ForkJoinPool and ForkJoinTask

  • ForkJoinPool
ForkJoinPool pool = new ForkJoinPool(numberOfProcessors);
  • ForkJoinTask: 2 impl

    • RecursiveAction: does not return a value
    • RecursiveTask: returns an object of specified type
public class MyForkJoinTask extends RecursiveAction {
    @Override
    protected void compute() {
        . . . // your problem invocation goes here
    }
}

To execute:

pool.invoke(task);

Others

When wait is invoked, the thread releases the lock and suspends execution

At some future time, another thread will acquire the same lock and invoke Object.notifyAll, informing all threads waiting on that lock that something important has happened:

Allowing a thread to acquire the same lock more than once enables reentrant synchronization.

  • Reads and writes are atomic for reference variables and for most primitive variables (all types except long and double).
  • Reads and writes are atomic for all variables declared volatile (including long and double variables).

a static method is associated with a class, not an object. In this case, the thread acquires the intrinsic lock for the Class object associated with the class. Thus access to class's static fields is controlled by a lock that's distinct from the lock for any instance of the class.