简体   繁体   中英

Thread safe collection with fixed capacity in FIFO order

Problem: Maintain a collection with a fixed capacity ( say 2 elements ), which is accessible across 100+ threads concurrently.

Always store latest elements from recent thread. Once they are stored, write a method to check if all these elements are duplicate.

My solution : BlockingQueue with fixed capacity and implement custom add method.

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.Iterator;

public class FixedBlockingQueue<T extends Object> {

    final BlockingQueue<T> queue;
    private int capacity;

    public FixedBlockingQueue(int capacity){
        super();
        this.capacity = capacity;
        queue = new ArrayBlockingQueue<T>(capacity);
        System.out.println("Capactiy:"+this.capacity);
    }
    public void addElement(T element){
        try{
            if ( queue.size() > capacity - 1 ){
                queue.remove();         
            }
            queue.put(element);
            for ( Iterator it = queue.iterator(); it.hasNext();){
                System.out.println(it.next());
            }
            System.out.println("________");
        }catch(Exception err){
            err.printStackTrace();
        }
    }

    public static void main(String args[]){
        FixedBlockingQueue<Integer> f = new FixedBlockingQueue<Integer>(2);
        for ( int i=0; i< 10; i++){
            f.addElement(i);
        }

    }   
}

output:

0
________
0
1
________
1
2
________
2
3
________
3
4
________
4
5
________
5
6
________
6
7
________
7
8
________
8
9

From output, you can clearly see that first element is getting removed and recent element is getting added to Queue.

My query : Is it the good solution ? or is there any other nice solution, works better than this?

EDIT: In this scenario of frequent deletions, is ArrayBlockingQueue better than LinkedBlockingQueue ?

Let's avoid reinventing the wheel, just use a LinkedBlockingQueue with a fixed capacity, it is a thread safe FIFO BlockingQueue . More details here .

The problem with your code is that you do the following operations not atomically such that you can face race condition issues with it:

if ( queue.size() > capacity - 1 ){
    queue.remove();         
}
queue.put(element);

You need to wrap it into a synchronized block or use an explicit Lock to protect it as it is a critical section and we don't want multiple threads to call it concurrently.

Here is how it can be done with a BlockingQueue :

BlockingQueue queue = new LinkedBlockingQueue(2);
for ( int i=0; i< 10; i++){
    // Try to add the object and return immediately if it is full
    // then if it could not be added,
    // remove the last element of the queue and try again
    while (!queue.offer(i, 0L, TimeUnit.MICROSECONDS)) {
        queue.remove();
    }
    for ( Iterator it = queue.iterator(); it.hasNext();){
        System.out.println(it.next());
    }
    System.out.println("________");
}

Output:

0
________
0
1
________
1
2
________
2
3
________
3
4
________
4
5
________
5
6
________
6
7
________
7
8
________
8
9
________

I have to admit first that I never used a BlockingQueue from the concurrent package, but I have done multi-threaded programming before.

I believe there's a problem here:

if ( queue.size() > capacity - 1 ){
  queue.remove();         
}

If there are multiple threads running this method concurrently, then multiple threads could do this check and it could evaluate to true for a good number of them before they take action. So in that case, remove() could be called more times than you anticipate.

Basically, if you want to keep your logic that way, you will have to find a way to ensure that it isn't possible for another Thread to change the size of the queue between the time you check the size and then perform an operation on it, such as removing an element.

One way to fix this might be to wrap it in a synchronized block like so:

synchornized (queue) {
  if ( queue.size() > capacity - 1 ){
    queue.remove();         
  }
  queue.put(element);
  for ( Iterator it = queue.iterator(); it.hasNext();){
    System.out.println(it.next());
  }
  System.out.println("________");
}

This ensures that queue doesn't get accessed by other threads after you check the current size of it. Keep in mind that the more stuff that you have synchronized, the longer that other threads will have to wait before performing operations on it, which could slow down your program. You can read more about this keyword here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM