Core Java

Java Concurrency: The Lock interface

Previously we implemented a thread safe counter using synchronized. We would like to swift from synchronized blocks to something more flexible with more features, this is were locks are of use. On this blog we will focus on Java’s Lock interface.

The Lock interface supports three forms of lock acquisition interruptible, non-interruptible, and timed.

We can acquire locks between threads. A thread will wait until the lock is released from the thread holding the lock:

@Test
    void lock() throws InterruptedException {
        Thread withDelayedLock = new Thread(() -> {
            lock.lock();
            log.info("Acquired delayed");
            lock.unlock();
        });

        lock.lock();
        withDelayedLock.start();

        Thread.sleep(500);
        log.info("Will release");
        lock.unlock();
        withDelayedLock.join();
    }

Since the threads blocks on the lock we might as well interrupt the thread. In this case we can lock lockInterruptibly, and apply any logic in case of an Interrupt:

@Test
    void lockInterruptibly() throws InterruptedException {
        Thread withDelayedLock = new Thread(() -> {
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                log.error("interrupted while waiting",e);
            }
        });

        lock.lock();
        withDelayedLock.start();
        withDelayedLock.interrupt();
        lock.unlock();
        withDelayedLock.join();
    }

A thread can also try to acquire a lock which is already acquired, and exit immediately instead of blocking.

@Test
    void tryLock() throws InterruptedException {
        Thread withDelayedLock = new Thread(() -> {
            boolean locked = lock.tryLock();
            assertFalse(locked);
        });

        lock.lock();
        withDelayedLock.start();
        Thread.sleep(500);
        lock.unlock();
        withDelayedLock.join();
    }

Also a time period can be specified until the lock is acquired.

@Test
    void tryLockTime() throws InterruptedException {
        Thread withDelayedLock = new Thread(() -> {
            try {
                boolean locked = lock.tryLock(100, TimeUnit.MILLISECONDS);
                assertFalse(locked);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        lock.lock();
        withDelayedLock.start();
        Thread.sleep(500);
        lock.unlock();
        withDelayedLock.join();
    }

Another thing of importance with locks is memory.

From the documentation

All {@code Lock} implementations <em>must</em> enforce the same
* memory synchronization semantics as provided by the built-in monitor
* lock, as described in
* Chapter 17 of
* <cite>The Java Language Specification</cite>:
*
<ul>*
 	<li>A successful {@code lock} operation has the same memory
* synchronization effects as a successful <em>Lock</em> action.
*</li>
 	<li>A successful {@code unlock} operation has the same
* memory synchronization effects as a successful <em>Unlock</em> action.
*</li>
</ul>

In order for the results to be flushed on the main memory we need to use lock and unlock.
Take the following example

private Lock lock = new ReentrantLock();
    private String threadAcquired = "main";

    @Test
    void wrongMemoryVisibility() throws InterruptedException {
        Thread withDelayedLock = new Thread(() -> {
            lock.lock();
            try {
                threadAcquired = "delayed";
                System.out.println("Acquired on delayed");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }

        });

        lock.lock();
        try {
            withDelayedLock.start();
            Thread.sleep(500);
        } finally {
            lock.unlock();
        }

        while (true) {
            System.out.println("Currently acquired " + threadAcquired);
            if (threadAcquired.equals("delayed")) {
                break;
            }
        }

        withDelayedLock.join();
        threadAcquired = "main";
    }

We print the variable threadAcquired by the main thread while it is changed by the thread with a Delayed lock.
After a few runs a situation where the threadAcquired variable has a stale value on the main thread will appear.

Acquired on delayed
Currently acquired main

We did this on purpose, on a real world problem we should not access variables like threadAcquired without proper synchronisation, for example we should have acquired the lock first, this way the memory would be synchronised and we would have an up to date value.

@Test
    void correctMemoryVisibility() throws InterruptedException {
        Thread withDelayedLock = new Thread(() -> {
            lock.lock();
            try {
                threadAcquired = "delayed";
                System.out.println("Acquired on delayed");
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }

        });

        lock.lock();
        try {
            withDelayedLock.start();
            Thread.sleep(500);
        } finally {
            lock.unlock();
        }

        while (true) {
            lock.lock();
            try {
                System.out.println("Currently acquired " + threadAcquired);
                if (threadAcquired.equals("delayed")) {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }

        withDelayedLock.join();
    }

That’s all for now, we will later proceed on different type of locks and the Condition interface.

Published on Java Code Geeks with permission by Emmanouil Gkatziouras, partner at our JCG program. See the original article here: Java Concurrency: The Lock interface

Opinions expressed by Java Code Geeks contributors are their own.

Emmanouil Gkatziouras

He is a versatile software engineer with experience in a wide variety of applications/services.He is enthusiastic about new projects, embracing new technologies, and getting to know people in the field of software.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button