Locks in Java
A few examples and notes

Nicholas Duchon, Jan 5, 2015.

Outline

Externals

General

The problem is controlled and efficient access to shared resources.

Java support

Type
Class or Package
Links to API - SE 8
Threads
java.lang.Thread
Thread - start, run, sleep, join, yield
Monitor java.lang.Object Object - wait, notify, notifyAll
Chapter 8. Classes - synchronized reference in Language Specification
Lock interface java.util.concurrent.locks locks
Atomic single variable updates java.util.concurrent.atomic atomic
Thread-safe data structures java.util.concurrent concurrent
Low level machine interface
sun.misc.Unsafe
FAQ - Sun Packages - Unsafe is exactly what its name implies
Understanding sun.misc.Unsafe | Javalobby some of its features
Download Slides - Oracle - 2014 pdf presentation on ideas for locks

Object class

Each object in Java (including instances of Class class) has ONE lock, called a monitor, which is handled through the synchronized(Object) keyword and the wait/notify/notifyAll methods.

Here is a typical approach to using these locks:

  1. synchronized (X) {
  2.   while (X.busyFlag) {
  3.     try {
  4.       X.wait();
  5.     }
  6.     catch (InterruptedException e) {
  7.     } // end try/catch block
  8.   } // end while waiting for worker to be free
  9.   X.busyFlag = true;
  10. } // end synchronized on X
  11. // do stuff, not needing synchronization
  12. // later:
  13. synchronized (X) {
  14.   X.busyFlag = false;
  15.   X.notifyAll ();
  16. } // end synchronized on X

Comments:

  1. This code can be used to make sure that only one thread has access to X at a time, while minimizing the resources used by threads that don't have access to X.
  2. In this example, the entire focus is on the control of access to the variable X.busyFlag in a multi-threaded environment.
  3. (lines 1-10 and 13-16): Request lock on object X (may be an instance of a class or the class definition). This will block if some other thread is holding this lock - the monitor for this object.
  4. (lines 2-8) Check some condition, only proceed if condition is not met. In this case, the X class has a boolean variable busyFlag.
  5. (lines 3-8) try/catch block related to the wait() method call on line 4.
  6. (line 4) Since the object X was not available (the condition on line 2 was true), this thread will go into a wait state, taking it off the thread scheduler's processes ready to run list.
    1. The wait() operation RELEASES the monitor lock, and blocks until this thread receives a notify() call, or a notifyAll() is executed by another thread.
    2. When this thread is awoken by the O/S, the thread first tries to acquire the monitor lock on the object X.
      1. If acquiring the lock fails, this thread will block waiting for the lock to be released through an end of some other synchronize block on this object.
        In other words, this thread re-executes the wait() method.
      2. If acquiring the lock succeeds, this code will exit the try/catch block, to line 8, which returns the execution to line 2.
  7. (line 9) if the code gets here, THIS thread has sole access to X, and now is the time to mark this object as owned by this thread
  8. (line 10) The end of the synchronize block, which will release the monitor lock on the object X.
  9. (line 11) This is the time to do something that may be long and complicated, but will not impact any other thread.
  10. (lines 13-16) Again request the lock on object X.
  11. (line 14) Change the condition variable used by X.
  12. (line 15) Notify other threads that the condition of X has changed, and let the other threads try their luck in obtaining sole protected access to X.
  13. The monitor lock is held for very short times.
  14. The monitor methods (wait, notify and notifyAll) MUST be inside a synchronized block of the corresponding object - in this example, that object is X.

Locks

Locks interface
ReentrantLock Class
Condition interface
Required & common
Specialized Methods
lock ()
lockInterruptibly ()
newCondition ()
tryLock ()
tryLock (long, TimeUnit)
unlock ()
lock ()
lockInterruptibly ()
newCondition ()
tryLock ()
tryLock (long, TimeUnit)
unlock ()

isLocked ()
toString ()
getHoldCount ()
getOwner ()
getQueuedThreads ()
getQueueLength ()
getWaitingThreads (Condition)
getWaitQueueLength (Condition)
hasQueuedThread (Thread)
hasQueuedThreads ()
hasWaiters (Condition)
isFair ()
isHeldByCurrentThread ()
await ()
await (long, TimeUnit)
awaitNanos (long)
awaitUninterruptibly ()
awaitUntil (Date)
signal ()
signalAll ()


Lock Trace Example

Here's a trace of the methods called when a myLock.lock() call is made on the ReentrantLock myLock.

Locking Examples

public class TaskThreadDemo {
  public static void main (String args []) {
    String [] sa = {"a", "X", "+", "."};
    for (String s: sa) {
      Runnable ps = new PrintChar (s, 200);
      Thread ts = new Thread (ps, s);
      ts.start ();
    } // end for each character
 
} // end main
} // end class TaskThreadDemo

class PrintChar implements Runnable {
  String ch;
  int times;
 
  public PrintChar (String c, int n) {
    ch = c;
    times = n;
 
} // end constructor
 
  public void run () {
    for (int i = 0; i < times; i++) {
      System.out.print (ch);
      if (i%30 == 29) System.out.println ();
    } // end for loop
 
} // end method run
} // end class PrintChar

public class TaskThreadDemo2 {
  public static void main (String args []) {
    String [] sa = {"a", "X", "+", "."};
    Thread ts;
    for (String s: sa) {
      Runnable ps = new PrintChar2 (10_000_000);
      ts = new Thread (ps, s);
      ts.start (); // start () vs run() !
    } // end for each character
  } // end main
} // end class TaskThreadDemo

class PrintChar2 implements Runnable {
  static int sum; // try without static !
  int times;
 
  public PrintChar2 (int n) {
    times = n;
  } // end constructor
 
  // try synchronized : method, this, class
  public void run () {
    for (int i = 0; i < times; i++) {
      sum = sum + 1;
//       synchronized (PrintChar2.class) { sum = sum + 1;}
//       synchronized (this) { sum = sum + 1;}
    } // end for loop
    System.out.printf ("%s: %,d\n",
      Thread.currentThread().getName(),
      sum);
  } // end method run
} // end class PrintChar

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TaskThreadDemo3 {

  public static void main (String args []) {
    String [] sa = {"a", "X", "+", "."};
    Thread ts;
    for (String s: sa) {
      Runnable ps = new PrintChar3 (10_000_000);
      ts = new Thread (ps, s);
      ts.start (); // start () vs run() !
    } // end for each character
  } // end main
} // end class TaskThreadDemo3

class PrintChar3 implements Runnable {
  static Lock myLock = new ReentrantLock ();
  static int sum; // try without static !
  int times;
 
  public PrintChar3 (int n) {
    times = n;
  } // end constructor
 
  // try locking : method, this, class
  public void run () {
    for (int i = 0; i < times; i++) {
//       sum = sum + 1;
      {myLock.lock(); sum = sum + 1; myLock.unlock();}
    } // end for loop
    System.out.printf ("%s: %,d\n",
      Thread.currentThread().getName(),
      sum);
  } // end method run
} // end class PrintChar3

import java.util.concurrent.atomic.LongAdder;

public class TaskThreadDemo4 {
  public static void main (String args []) {
    String [] sa = {"a", "X", "+", "."};
    Thread [] ts = new Thread [sa.length];

    for (int i = 0; i < ts.length; i++) {
      Runnable ps = new PrintChar4 (10_000_000);
      ts[i] = new Thread (ps, sa[i]);
      ts[i].start ();
    } // end start a thread for each character

    for (int i = 0; i < ts.length; i++) {
      try {ts[i].join ();}
      catch (InterruptedException e) {}
    } // end for i - wait until all threads are done

    System.out.printf ("Final sum: %,d\n", PrintChar4.sum.intValue());

 
} // end main

} // end class TaskThreadDemo4

class PrintChar4 implements Runnable {
  static LongAdder sum = new LongAdder ();
  int times;
 
  public PrintChar4 (int n) {
    times = n;
  } // end constructor
 
   public void run () {
    for (int i = 0; i < times; i++) {
      sum.add(1);
    } // end for loop
    System.out.printf ("%s: %,d\n",
      Thread.currentThread().getName(),
      sum.intValue());
 
} // end method run
} // end class PrintChar4

More Comments

As for why locks might be better than synchronized methods, and vs synchronized blocks - let me give it a try.

First, a synchronized method is actually synchronizing on "this" - in an instance method, the object that was the context of the method call. In a static method, the class (as an object).

A synchronized block will explicitly synchronize on some object: synchronize (this) or synchronize (X), where X is some object in scope.

In this context, a lock creates a completely different object and synchronizes on that object.

Even more to the point - Java currently provides only THREE classes that implement the Lock interface:

And they all are based on the idea of a queue holding the threads that attempt to assert this lock - which gives a kind of fairness to the system.

And then there's the substitute for the wait/notify/notifyAll protocol of synchronize, based on conditions - but again Condition is an interface, with only two current implementations:

And writing your own implementations of these interfaces seems amazingly daunting.

SO:

Instead of creating one's own conditions, like I did in the following example using the X.busy boolean flag:

With locks, one can define conditions in a more intuitive and integral way - notice the two conditions defined in the example and how they are used - and that the conditions are tied to the lock:

I know this is rather long, but the issues take quite a bit of time to explain and they are rather subtle.

I hope thus helps,

Summaries

Locks package Atomic package

Interfaces Classes Classes

Condition
Lock
ReadWriteLock
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicIntegerFieldUpdater
AtomicLong
AtomicLongArray
AtomicLongFieldUpdater
AtomicMarkableReference
AtomicReference
AtomicReferenceArray
AtomicReferenceFieldUpdater
AtomicStampedReference
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder

Concurrent Package
Interfaces Classes Enums Exceptions
BlockingDeque
BlockingQueue
Callable
CompletableFuture.AsynchronousCompletionTask
CompletionService
CompletionStage
ConcurrentMap
ConcurrentNavigableMap
Delayed
Executor
ExecutorService
ForkJoinPool.ForkJoinWorkerThreadFactory
ForkJoinPool.ManagedBlocker
Future
RejectedExecutionHandler
RunnableFuture
RunnableScheduledFuture
ScheduledExecutorService
ScheduledFuture
ThreadFactory
TransferQueue
AbstractExecutorService
ArrayBlockingQueue
CompletableFuture
ConcurrentHashMap
ConcurrentHashMap.KeySetView
ConcurrentLinkedDeque
ConcurrentLinkedQueue
ConcurrentSkipListMap
ConcurrentSkipListSet
CopyOnWriteArrayList
CopyOnWriteArraySet
CountDownLatch
CountedCompleter
CyclicBarrier
DelayQueue
Exchanger
ExecutorCompletionService
Executors
ForkJoinPool
ForkJoinTask
ForkJoinWorkerThread
FutureTask
LinkedBlockingDeque
LinkedBlockingQueue
LinkedTransferQueue
Phaser
PriorityBlockingQueue
RecursiveAction
RecursiveTask
ScheduledThreadPoolExecutor
Semaphore
SynchronousQueue
ThreadLocalRandom
ThreadPoolExecutor
ThreadPoolExecutor.AbortPolicy
ThreadPoolExecutor.CallerRunsPolicy
ThreadPoolExecutor.DiscardOldestPolicy
ThreadPoolExecutor.DiscardPolicy
TimeUnit BrokenBarrierException
CancellationException
CompletionException
ExecutionException
RejectedExecutionException
TimeoutException