5

I have a critical section, I need to control that only those threads with a given attribute value are able to enter at the same time.

For example: I have Thread#1 that handles products, Thread#2 handles products too and Thread#3 handles services

now:

  1. T1 comes first and enters the critical section, so that the product is handled

  2. T2 wants to enter, but since there is another product being processed, it must wait

  3. T3 comes last, it is able to enter, since it needs a service (not a product) to be processed

  4. T1 goes out, now T2 is able to get in

I think it looks quite simple, but I wasn't able to find anything that fits the requirement. My question is, how can I achieve this? any reference to any source of information about this will be appreciated

Thank you very much in advance

5
  • 3
    Sounds like poor design. Locking shouldn't be based on thread ids. Commented Aug 22, 2016 at 17:42
  • That not makes sense, you need to block by actions not by Thread-ID. For example Read/write actions. Commented Aug 22, 2016 at 17:43
  • Re, "I think it looks quite simple." I disagree. If it's safe for one thread with each different ID to enter the critical section, but not safe for two threads with the same ID, that seems to imply that threads with different IDs are doing different things. That sounds like a horrible idea. Your critical sections should be as small as you can possibly make them, and the code should just do one thing. Commented Aug 22, 2016 at 17:53
  • 2
    It doesn't seem the author means real Thread IDs, otherwise they wouldn't have the same values. It seems that ID is just sort of an attribute associated with a thread. @McCoy, could you, please, clarify on how do you assign IDs to threads? Commented Aug 22, 2016 at 17:56
  • Sorry, my bad, I tried to put it simple to understand and I see I've made a mess. @AndrewLygin is right, that is what I meant. I'll edit the explanation Commented Aug 22, 2016 at 17:58

4 Answers 4

7

How about this:

private ConcurrentMap<Integer, Semaphore> semaphores = new ConcurrentHashMap<>();

public void enter(int id) {
    Semaphore s = semaphores.computeIfAbsent(id, key -> new Semaphore(1));

    try {
        s.acquire();
        // Critical section.
    } catch (InterruptedException e) {
        // Exception handling.
    } finally {
        s.release();
    }
}
  • The hash map guarantees fast access and can grow dynamically. Furthermore, you're able to use objects as keys.
  • If you know the number of IDs at compile time, you can also use an unmodifiable or immutable map and prefill it.
  • The semaphore can be adjusted as well: set different numbers of permits and fairness guarantees for different IDs.
  • The wrapping class can provide additional methods, e.g. a non-blocking tryEnter(int) via Semaphore#tryAcquire().
Sign up to request clarification or add additional context in comments.

Comments

2

I think you cannot lock on value types but try this workaround: Put lock objects in an array and access the array from ID.

Something like:

private Object[] locks = new Object[] {new Object(), new Object()};

private void yourMethod(int id)
{
    synchronized(locks[id]) //or id - 1 if you want to stick to your 1 and 2
    {
        //do your things
    }
}

Hope it helps

3 Comments

There are far superior constructs available. I wouldn't start writing any lock arrays for no reason.
I was thinking something similar, like a pool of locks, but I thought maybe there is some well known pattern to sort out these kind of scenarios
@Kayaman I agree but I'm answering the question as it was originally. If you think you can help him better by leading him to another way do it.
1

If you really need this logic, it can be implemented this way:

private final Lock lock = new Lock();

public void process() {
    boolean locked = false;
    if (isProduct()) {   // Depends on the current thread
        lock.lock();
        locked = true;
    }

    try {
        // Do the work here
    } finally {
        if (locked) {
            lock.unlock();
        }
    }
}

But the question itself tells about a poor design. Just create methods processProduct() and processService(), make the first one synchronized and call the one you really need from your threads.

1 Comment

I agree it's a poor design, the thing is that it wasn't thought to work this way. Anyway, this answer could work, but I have many different values that will determine if a thread is able to enter or not, not only "isProduct" and "isService". What I need is like different queues to line up threads according some obects they carry
1

We sorted this issue out differently, with a high performance impact, using an ordinary lock which will be a bottleneck under some scenarios. But I managed to get a solution, not tested though. This consists of a Lock class which contains a pool of Locks and provides them to threads according a termId. All threads who share the same termId will be provided with the same Lock. This way we'll get all threads grouped by termId, all lined up in different queues. I set a fixed pool of locks, though I think it could be dynamic also,removing and adding Locks on demand.

Any feedback will be appreciated

import java.util.ArrayList;
import java.util.concurrent.Semaphore;

public class Lock {

  private Lock() {}

  private static class SingletonHolder {

    /** ordered list. Locks with termId null must be at the end **/
    public static final ArrayList<Lock> INSTANCES = new ArrayList<Lock>(){{
      add(new Lock());
      add(new Lock());
      add(new Lock());
      add(new Lock());
      add(new Lock());
      add(new Lock());
      add(new Lock());
    }};

    public static synchronized Lock returnInstance(String termId){
      ArrayList<Lock> sortedList = sortInstances();
      for(Lock instance: sortedList){
        if(termId.equals(instance.getTermId()))
          return instance;
        if(instance.getTermId()==null)
          return instance;
      }

      return null;
    }

    public static ArrayList<Lock> sortInstances(){
      ArrayList<Lock> sortedList=new ArrayList<Lock>();
      for(Lock instance: INSTANCES){
        if(instance.getTermId()==null)
          sortedList.add(instance);
        else
          sortedList.add(0,instance);
      }

      return sortedList;
    }
  }

  /**
  * Use this method to get a reference to a singleton instance of
  * {@link Lock}
  * 
  * @return a singleton instance
  */
  public static Lock getInstance(String termId) {
    return SingletonHolder.returnInstance(termId);
  }

  private Semaphore lock = new Semaphore(1);
  private String termId;

 public void getLock(String termId) throws InterruptedException {
   lock.acquire();
   setTermId(termId);
 }

 public void releaseLock() {   
   lock.release();
   setTermId(null);
 }

  public String getTermId() {
    return termId;
  }

  public void setTermId(String termId) {
    this.termId = termId;
  }
} 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.