10

At API docs of Lockinterface for method tryLock(), this code sample is pasted,

A typical usage idiom for this method would be:

  Lock lock = ...;
  if (lock.tryLock()) {
      try {
          // manipulate protected state
      } finally {
          lock.unlock();
      }
  } else {
      // perform alternative actions
  }

My question is , does this use case not existed before Java 5 or folks used to implement it via some other techniques?

I am not able to comprehend the need to execute perform alternative actions based on lock availability.

Can somebody please explain real use cases for this?

I am sure this technique is not a straight forward replacement of synchronizedto write deadlock free code.

Sabir Khan
  • 9,826
  • 7
  • 45
  • 98
  • 2
    To down voter and the person who voted to close it, please revert your actions if you feel so since its been more than two days and you don't have enough takers. Two answers posted here provide very useful insights and I wish my question to be noticed but I guess negative vote is a hurdle since most folks don't like to answer a down voted question. Never mind if you don't feel so. – Sabir Khan Jan 24 '17 at 15:00

4 Answers4

3

One straight-forward use case is a thread processing a batch of elements, occasionally trying to commit the elements that have been processed. If acquiring the lock fails, the elements will be committed in the next successful attempt or at the final, mandatory commit.

Another example can be found within the JRE itself, ForkJoinTask.helpExpungeStaleExceptions() is a method for performing a task that can be done by an arbitrary thread, but only one at a time, so only the one thread successfully acquiring the lock will perform it, all others will return, as the unavailability of the lock implies that there is already a thread performing the task.


It is possible to implement a similar feature before Java 5, if you separate the intrinsic locking feature, which doesn’t support being optional, from the locking logic, that can be represented as an ordinary object state. This answer provides an example.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    Can you provide straight-forward example? Description is not obvious for me – gstackoverflow Mar 03 '17 at 10:27
  • I am not expert in ForkJoinPool internals thus it is hard enough to understand why tryLock was used at this case – gstackoverflow Mar 03 '17 at 10:46
  • Well, as I already said, `expungeStaleExceptions()` is “*a method for performing a task that can be done by an arbitrary thread, but only one at a time*”. It’s not necessary to know more details about that. I don’t see, how I could help you, if you don’t understand it. Any real life example will be too complicated and any example I could post would be too contrived. – Holger Mar 03 '17 at 10:50
  • this description respects the usual mutual exlusion like synchronized – gstackoverflow Mar 03 '17 at 14:01
  • Yes, this is a mutual exclusion, like with `synchronized`. The difference is that with `synchronized`, all threads would be blocked until the one performing the work has finished, while with `tryLock`, all other threads return to the caller immediately when someone else is already doing the work. – Holger Mar 03 '17 at 16:53
3

My question is, does this use case not existed before Java 5 or folks used to implement it via some other techniques?

The Lock interface was added in Java 5, is that what you mean? Not sure what was there before.

I am not able to comprehend the need to execute perform alternative actions based on lock availability. Can somebody please explain real use cases for this?

Sure. Just wrote one of these today actually. My specific Lock implementation is a distributed lock that is shared among a cluster of servers using the Jgroups protocol stack. The lock.tryLock(...) method makes RPC calls to the cluster and waits for responses. It is very possible that multiple nodes maybe trying to lock and their actions might clash causing delays and certainly one lock to fail. This either could return false or timeout in which case my code just waits and tries again. My code is literally:

if (!clusterLock.tryLock(TRY_LOCK_TIME_MILLIS, TimeUnit.MILLISECONDS)) {
    logger.warn("Could not lock cluster lock {}", beanName);
    return;
}

Another use case might be a situation where one part of the code holds a lock for a large amount of time and other parts of the code might not want to wait that long and instead want to get other work done.

Here's another place in my code where I'm using tryLock(...)

// need to wait for the lock but log
boolean locked = false;
for (int i = 0; i < TRY_LOCK_MAX_TIMES; i++) {
    if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
        logger.debug("Lock worked");
        locked = true;
        break;
    } else {
        logger.debug("Lock didn't work");
    }
}
Gray
  • 115,027
  • 24
  • 293
  • 354
  • Hi Gray! Could you share some info about your "shared among a cluster of servers" Lock? I'm curious to understand how something like that could be built! Really! Thanks! – LppEdd Oct 06 '18 at 22:22
  • It was based on Jgroups @LppEdd. They have a number of cluster operations like distributed locks in their base code. – Gray Oct 11 '18 at 16:06
2

The reason for writing code like that example is if you have a thread that is doing more than one job.

Imagine you put it in a loop:

while (true) {
    if (taskA_needsAttention() && taskA_lock.tryLock()) {
        try {
            ...do some work on task A...
        } finally {
            taskA_lock.unlock();
        }
    } else if (taskB_needsAttention() && taskB_lock.tryLock()) {
        try {
            ...do some work on task B...
        } finally {
            taskB_lock.unlock();
        }
    } else ...
}

Personally, I would prefer not to write code like that. I would prefer to have different threads responsible for task A and task B or better still, to use objects submitted to a thread pool.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
1

Use case 1

One use case would be to completely avoid running the thread. Like in the example below, for example a very strict internet hotspot where you can only access one webpage at once, and other requests are cancelled.

With synchronized you cannot cancel it, since it waits until it can obtain the lock. So tryLock just gives you flexibility to cancel something or to run other behavior instead.

package Concurrency;

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

public class LimitedHotspotDemo
{
    private static Lock webAccessLock = new ReentrantLock();

    private static class AccessUrl implements Runnable
    {
        private String url;

        public AccessUrl(String url)
        {
            this.url = url;
        }

        @Override
        public void run()
        {
            if(webAccessLock.tryLock()) {
                System.out.println("Begin request for url " + url);
                try {
                    Thread.sleep(1500);
                    System.out.println("Request completed for " + url);
                } catch (InterruptedException e) {
                    webAccessLock.unlock();
                    return;
                } finally {
                    webAccessLock.unlock();
                }
            } else {
                System.out.println("Cancelled request " + url + "; already one request running");
            }
        }
    }

    public static void main(String[] args)
    {
        for(String url : Arrays.asList(
            "https://www.google.com/",
            "https://www.microsoft.com/",
            "https://www.apple.com/"
        )) {
            new Thread(new AccessUrl(url)).start();
        }
    }
}

Output:

Begin request for url https://www.microsoft.com/
Cancelled request https://www.google.com/; already one request running
Cancelled request https://www.apple.com/; already one request running
Request completed for https://www.microsoft.com/

Use case 2

Another use case would be a light sensor which keeps the light on when there is movement in the room (with Sensor thread). There is another thread (TurnOffLights) running to switch the light off when there is no more movement in the room for a few seconds.

The TurnOffLights thread uses tryLock to obtain a lock. If no lock can be obtained, the process is delayed for 500ms. The last Sensor thread is blocking the lock for 5 seconds, after which the TurnOffLights thread can obtain the lock and turn off the lights.

So in this case the TurnOffLights thread is only allowed to turn off the lights when there are no more signals to the Sensor for 5 seconds. The TurnOffLights thread is using tryLock to obtain the lock.

package Concurrency;

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

public class LightSensorDemo
{
    private static volatile Lock lock = new ReentrantLock();
    private static volatile Thread lastSignal = null;
    private static Sensor sensor = new Sensor();

    private static class Sensor implements Runnable
    {
        private static Boolean preparing = false;
        public static Boolean isPreparing()
        {
            return preparing;
        }

        @Override
        public void run()
        {
            System.out.println("Signal send " + Thread.currentThread().getName());
            try {
                invalidatePreviousSignalsAndSetUpCurrent();
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                //System.out.println("Signal interrupted " + Thread.currentThread().getName());
                return;
            } finally {
                lock.unlock();
            }
        }

        private static synchronized void invalidatePreviousSignalsAndSetUpCurrent() throws InterruptedException
        {
            preparing = true;
            if(lastSignal != null) {
                lastSignal.interrupt();
            }
            lastSignal = Thread.currentThread();
            lock.lockInterruptibly();
            preparing = false;
        }
    }

    private static class TurnOffLights implements Runnable
    {
        @Override
        public void run()
        {
            while(true) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    System.out.println("Interrupted" + this.getClass().getName());
                    return;
                }
                if (!Sensor.isPreparing()) {
                    if(lock.tryLock()) {
                        try {
                            System.out.println("Turn off lights");
                            break;
                        } finally {
                            lock.unlock();
                        }
                    }  else {
                        System.out.println("Cannot turn off lights yet");
                    }
                } else {
                    System.out.println("Cannot turn off lights yet");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        Thread turnOffLights = new Thread(new TurnOffLights());
        turnOffLights.start();

        //Send 40 signals to the light sensor to keep the light on
        for(int x = 0; x < 10; x++) {
            new Thread(sensor).start(); //some active movements
            new Thread(sensor).start(); //some active movements
            new Thread(sensor).start(); //some active movements
            new Thread(sensor).start(); //some active movements
            Thread.sleep(250);
        }

        turnOffLights.join();
    }
}

Notice also that I use lock.lockInterruptibly(); to interrupt previous signals. So the 5 second countdown always starts from the last signal.

Output is something like:

...
Cannot turn off lights yet
Cannot turn off lights yet
Signal send Thread-19
Signal send Thread-20
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Cannot turn off lights yet
Turn off lights

Process finished with exit code 0
Douma
  • 2,682
  • 14
  • 23