javamultithreadingarraylistconcurrencyjava.util.concurrent

How to ensure an unique item/object is picked from the collection in multi-threading scenario?


I have a collection of string values available.

I thought of using CopyOnwriteArrayList or ConcurrentLinkedQueue initially. But, when I run my code, I see that same string values are picked by one more threads most of the time.

Here is my working code with the lock to serve the purpose:

public class SomeClass {

  ReentrantLock lock = new ReentrantLock();
  // this list is ensured to have non-duplicate items.
  private List<String> bookingIds;

  public String read() {
    lock.lock();
    if (Objects.isNull(bookingIds)) {
      bookingIds = bookingInfoUtil.findDistinctId();
    }
    if (! bookingIds.isEmpty()) {
      String bookingId = bookingIds.remove(0);
      lock.unlock();
      return bookingId;
    }
    lock.unlock();
    return null;
  }
}

Now, I want to know how to achieve this with a lock-free mechanism, preferably with a thread-safe collection. As I mentioned above, I tried to use both CopyOnwriteArrayList or ConcurrentLinkedQueue and exposed it to a threadpool that actively call read() method to obtain a unique value. It always fails to attain this goal unless a lock is used. Any better ways to do this?


Solution

  • So, here is the approach that I used:

    1. Moved the data loading logic of bookingInfoUtil.findDistinctId() , outside of read() so that data is populated before threads reach to read() method.
    2. Since I'm modifying the list under multi-threading environment, I used a lock-free thread-safe queue, that is ConcurrentLinkedQueue, so that my performance is much better for insert/remove operation. Also, it doesn't have to re-shuffle the entire list of elements since it is a queue.

    So, the code is simplified to:

    public class SomeClass {
    
      // Value is actually set as a batch job parameter for my case.
      // But, you can also set this as a bean or load the values using constructor.
      @Value("#{jobParameters[bookingIds]}")
      ConcurrentLinkedQueue<String> bookingIds;
    
      public String read() {
        if (! bookingIds.isEmpty()) {
          return bookingIds.poll();
        }
        return null;
      }
    }