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.
Data Engineering Design Patterns
Looking for a book that defines and solves most common data engineering problems? I wrote
one on that topic! You can read it online
on the O'Reilly platform,
or get a print copy on Amazon.
I also help solve your data engineering problems 👉 contact@waitingforcode.com 📩
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:
- park() - blocks the execution of the current thread.
- time-based park - a thread can also be "parked" within specified delay: during some time (parkNanos(long)) or until some time (parkUntil(long))
- park with blocker - park methods allow to pass an object called blocker: (park(Object), parkNanos(Object, long), parkUntil(Object, long)). The blocker is assigned to blocked thread. But beware, it's not a kind of exclusive lock. More than that, the blocker is assigned to thread even before stopping it:
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); }Only with some extra work it could be used as for example a lock. It's shown in one of examples from the next part. - unpark(Thread) - unblocks given Thread, i.e. the permit is made available again.
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.
Consulting
With nearly 16 years of experience, including 8 as data engineer, I offer expert consulting to design and optimize scalable data solutions.
As an O’Reilly author, Data+AI Summit speaker, and blogger, I bring cutting-edge insights to modernize infrastructure, build robust pipelines, and
drive data-driven decision-making. Let's transform your data challenges into opportunities—reach out to elevate your data engineering game today!
👉 contact@waitingforcode.com
đź”— past projects

