Abstract

This article contains information about Concurrency applied to the world of Java in a compact format.

Concurrency basics

Parallelism in von-Neumann based machine architectures is actually just an emulated behaviour by process schedulers and needs further process synchronisation to work. As software engineer you usually have to coordinate thread calls of multiple threads to shared resources by hand to avoid logical (time-causal) issues and technical glitches.

But computer science and Java provides you an arsenal of algorithms, procedures and methods to handle these issues.

The classical way to secure critical resource accesses is an Instance Mutex (binary locking attribute for mutual exclusion) by applying a synchronized-block.

public class Dangerous {
     private final Object lock = new Object();

     public void callMe() {
          synchronized (lock) {
                // do something critical here
          }
     }
}

The locking can also be made on a method level by using a synchronized-method.

public class Dangerous {
     private final Object lock = new Object();

     public synchronized void callMe() {
          // do something critical here
     }
}

There is also another variant by using a Semaphore (Access-Counter Variable), that you can use if you need finer control about handling the number of threads. The concept of a Semaphore for process synchronisation was introduces by Edsger W. Djikstra in 1965.

A simple counting Semaphore class exists in Java since 1.5 and you can use the class like this:

public class Dangerous {
    // Construct an instance semaphore
    private final Semaphore sem = new Semaphore(1);

    public void callMe() {
         sem.acquire();
         try {
             // do something critical
         } finally {
             sem.release();
         }
    }

}

Avoiding Deadlocks

Deadlocks can occur if a thread waits infinite (usually for the finishing of another thread), because the processing scheduler never gives a thread any processing time. The waiting thread actually waits and dies in starvation. A cascade effect occurs if other threads wait for the result of the starved thread. This leads to an overall blocking system or application.

Symptoms

  • A hanging operating system or application

Causes

  • Synchronized Methods or Synchronized Blocks may lead to thread starvation / deadlock if these were called in many concurrent threads with the same priority and without a straight process ordering policy in the (JVM) scheduler

Solution

Use a ReentrantLock or ReentrantReadWriteLock. Both classes provide you a “fair-scheduling” semantics, that actually gives higher priority to the longest waiting thread.

You should use a ReentrantLock with a try-finally like this:

public class Dangerous {
    /**
     * A lock with fair-scheduling policy.
     */
    private final ReentrantLock lock = new ReentrantLock(true);

    public void callMe() {
       // Get a lock
       lock.lock();
       try {
           // do something critical
       } finally {
          // Unlock
          lock.unlock();
       }
    }
}