LockSupport in Java

Versions: Java 8

During reading Thread documentation I found a class that existence I've ignored until now - LockSupport. Some of its methods influence Thread states, so it was quite natural choice for the topic of next posts.

New ebook 🔥

Learn 84 ways to solve common data engineering problems with cloud services.

👉 I want my Early Access edition

As told, this post presents LockSupport. The first part explains its role and the API. The second part shows the use of LockSupport through several test cases.

LockSupport presentation

LockSupport is a class located in java.util.concurrent.locks package. Its neighbours are the classes and interfaces as: Lock, ReentrantLock or ReentrantWriteLock. Thanks to this neighbourhood it can be easily deduced that LockSupports is a kind of lock mechanism. The Javadoc confirms that by defining LockSupport as:

Basic thread blocking primitives for creating locks and other synchronization classes.

More specifically, LockSupport provides an alternative for some of Thread's deprecated methods: suspend() and resume(). It uses a concept of permit and parking to detect if given thread should block or not. Permit is associated to each class using LockSupport and is manipulated through park-like methods as:

LockSupport example

Following examples show the utility of LockSupport:

@Test
public void should_unblock_parked_thread() throws InterruptedException {
  List<Integer> iteratedNumbers = new ArrayList<>();
  Thread thread1 = new Thread(() -> {
    int i = 0;
    // park() blocks thread invoking this method
    LockSupport.park();
    while (true) {
      try {
        Thread.sleep(1_000L);
        iteratedNumbers.add(i);
        i++;
      } catch (InterruptedException e) {
        e.printStackTrace();
        Thread.currentThread().interrupt();
      }
    }
  });
  thread1.start();

  Thread thread2 = new Thread(() -> {
    try {
      Thread.sleep(2_600L);
    } catch (InterruptedException e) {
      e.printStackTrace();
      Thread.currentThread().interrupt();
    }
    // unpark(Thread) releases thread specified
    // in the parameter
    LockSupport.unpark(thread1);
  });
  thread2.start();

  Thread.sleep(5_000L);
  thread1.interrupt();

  assertThat(iteratedNumbers).hasSize(2);
  // Only 2 numbers are expected:
  // * thread1 blocks before starting the iteration
  // * thread2 wakes up after ~3 seconds and releases blocked thread1
  // * from 5 seconds allocated to execution, thread1 has only 2
  //   seconds to execute and since the sleep between iterations 
  //   is 1 second, it should make only 2 iterations
  assertThat(iteratedNumbers).containsOnly(0, 1);
}

@Test
public void should_block_thread_with_deadline() throws InterruptedException {
  List<Integer> iteratedNumbers = new ArrayList<>();
  Thread thread1 = new Thread(() -> {
    int i = 0;
    // park() blocks thread invoking this method
    long lockReleaseTimestamp = System.currentTimeMillis()+2_600L;
    LockSupport.parkUntil(lockReleaseTimestamp);
    while (true) {
      try {
        Thread.sleep(1_000L);
        iteratedNumbers.add(i);
        i++;
      } catch (InterruptedException e) {
        e.printStackTrace();
        Thread.currentThread().interrupt();
      }
    }
  });
  thread1.start();

  Thread.sleep(5_000L);
  thread1.interrupt();

  assertThat(iteratedNumbers).hasSize(2);
  // Only 2 numbers are expected because lock is held during ~3 seconds:
  // * thread1 blocks before starting the iteration during ~3 seconds
  // * from 5 seconds allocated to execution, thread1 has only 2 seconds
  //   of execution time and since the sleep between iterations is 1 second, 
  //   it should make only 2 iterations
  assertThat(iteratedNumbers).containsOnly(0, 1);
}

@Test
public void should_prove_that_blocker_is_not_an_exclusive_lock() throws InterruptedException {
  Object lock = new Object();
  boolean[] blocks = new boolean[2];
  Thread thread1 = new Thread(() -> {
    LockSupport.park(lock);
    blocks[0] = true;
  });
  thread1.start();

  Thread thread2 = new Thread(() -> {
    LockSupport.park(lock);
    blocks[1] = true;
  });
  thread2.start();

  Thread.sleep(2_000L);

  // Both threads stopped with the same blocker object (Object lock)
  // It shows that blocker can't work as an exclusive lock mechanism
  Object blockerThread1 = LockSupport.getBlocker(thread1);
  Object blockerThread2 = LockSupport.getBlocker(thread2);

  assertThat(blockerThread1).isEqualTo(lock);
  assertThat(blockerThread2).isEqualTo(lock);
  assertThat(blocks[0]).isFalse();
  assertThat(blocks[1]).isFalse();
}

@Test
public void should_implement_locking_mechanism_with_blocker() throws InterruptedException {
  Object lock = new Object();
  Thread thread1 = new Thread(() -> {
    LockSupport.parkUntil(lock, System.currentTimeMillis()+3_000L);
  });
  thread1.start();

  long timeBeforeLockAcquire = System.currentTimeMillis();

  // Give some guarantee to thread1 to acquire lock
  Thread.sleep(10L);

  // Try to lock current thread as long as
  // thread1 doesn't release its lock - let's suppose
  // that thread1 is making some job needed by current thread
  while (LockSupport.getBlocker(thread1) != null) {
  }
  LockSupport.parkUntil(lock, System.currentTimeMillis()+1_000L);
  long timeAfterLockRelease = System.currentTimeMillis();
  long duration = timeAfterLockRelease - timeBeforeLockAcquire;

  // Duration should be ~4 seconds because of 3 seconds of lock
  // acquired by thread1 and 1-second blocking of current thread
  assertThat(duration).isEqualTo(4_000L);
}

This post shows LockSupport. It's one of solutions to suspend and resume threads in Java. It represents this concept by the abstraction of parking/unparking which means making a permit available/unavailable (resume/suspend thread). It contains simple parking methods and also time-based. LockSupport makes also possible the use of blocker object which with a some of additional logic can be used as lock.