我必须使用ThreadSafe LinkedBlockingQueue,如果队列已满,它将从头部丢弃元素。
Apache提供了CircularFifoQueue,但这似乎不是线程安全的。
现在为了实现这一点,我正在更新从多个线程调用的类的write方法。
void addElement(Element e) throws InterruptedException {
if(queue.size()==MAX_SIZE)
{
queue.remove();
}
queue.put(e);
}
现在在内部检查尺寸,删除和放置使用锁的所有这三个操作。但是用我的方法,他们没有持有任何单一的锁。
现在,我的理解是,即使它们使用内部锁,但仍然在语句末尾释放这些锁。这3个操作不是原子的。
因此,有1个线程检查大小并继续删除的机会,但是由于现在有1个可用空间,其他线程最终插入了该元素。
请确认我的理解是否正确。还有没有在我的writeElement方法中放置通用锁的更好的方法来实现这一点。我不想放任何可以避免的锁,因为这种写方法是从多个线程调用的。
更新:另外,如果我像下面这样使用它,这是readWrite锁的正确用法。在这种情况下,我的目的是仅在我从队列中删除元素时才将写入操作与锁同步。
static final ReadWriteLock readWriteLockOnQueue = new ReentrantReadWriteLock();
void addElement(Element e) throws InterruptedException {
if(queue.size()==MAX_SIZE)
{
readWriteLockOnQueue.writeLock().lock();
queue.remove();
readWriteLockOnQueue.writeLock().unlock();
}
readWriteLockOnQueue.readLock().lock();
queue.put(e);
readWriteLockOnQueue.readLock().unlock();
}
答案 0 :(得分:0)
如果您确实证明性能问题是由阻止和上下文切换引起的,那么您可以自己实现CircularQueue
的非阻止版本。
您可以看一下Apache的实现,为了实现非阻塞线程安全行为,您需要进行的更改是必须将队列状态保留在单个原始变量(int
中, long
,或您认为方便的方式),然后将其CAS。因此,您的非阻塞队列如下所示:
public class CircularQueue<E> {
private final short maxSize;
private final AtomicLong ctl;
private final E[] elements;
public CircularQueue(short maxSize){
if(maxSize <= 0) throw new IllegalArgumentException();
this.maxSize = maxSize;
ctl = new AtomicLong(); //initial state, end = 0, start = 0, full = false, update in progress = false
elements = (E[]) new Object[maxSize];
}
public boolean addElement(E e){
final AtomicLong queueState = this.ctl;
boolean stateUpdated = false;
long insertIdx = 0;
long ctl = 0;
while(!stateUpdated) {
long currentState = queueState.get();
if ((currentState & FULL_BIT) != 0) {
return false;
} else {
insertIdx = (short) ((currentState & END_MASK) >>> END_SHIFT);
long newInsertIdx = insertIdx + 1;
if(newInsertIdx == maxSize)
newInsertIdx = 0;
if(newInsertIdx == (currentState & START_MASK))
ctl = ctlOfNewEndAndFull(currentState, newInsertIdx, true);
else
ctl = ctlOfNewEndAndFull(currentState, newInsertIdx, false);
stateUpdated = queueState.compareAndSet(currentState, ctl);
}
}
elements[(int) insertIdx] = e;
long mru = ((ctl & MOST_RECENT_UPD_ELEMENT_INDEX_MASK) >>> MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT);
while(((mru + 1) % maxSize) != insertIdx && mru != insertIdx){
ctl = queueState.get();
mru = (ctl & MOST_RECENT_UPD_ELEMENT_INDEX_MASK) >>> MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT;
}
while(!queueState.compareAndSet(ctl, setMru(ctl, insertIdx))){
ctl = queueState.get();
}
return true;
}
public E peek(){
final AtomicLong queueState = this.ctl;
long ctl = queueState.get();
long start = ctl & START_MASK;
long end = (ctl & END_MASK) >>> END_SHIFT;
boolean full = (ctl & FULL_BIT) != 0;
while(true) {
if (start == end & !full)
return null;
else if (queueState.compareAndSet(ctl, peekElemStateUpdate(ctl, (start + 1) % maxSize))){
return elements[(int) start];
} else {
ctl = queueState.get();
}
}
}
public static final long END_MASK = 0xFFFF00000000L; //short
public static final long END_SHIFT = 32;
public static final long MOST_RECENT_UPD_ELEMENT_INDEX_MASK = 0xFFFF0000L; //short
public static final long MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT = 16;
public static final long START_MASK = 0xFFFFL; //short
public static final long FULL_BIT = 1L << 63;
private static long peekElemStateUpdate(long oldState, long start){
return oldState & ~(START_MASK | FULL_BIT) | start;
}
private static long setMru(long oldState, long mru){
return (oldState & ~MOST_RECENT_UPD_ELEMENT_INDEX_MASK) | (mru << MOST_RECENT_UPD_ELEMENT_INDEX_SHIFT);
}
private static long ctlOfNewEndAndFull(long oldState, long newEnd, boolean full){
if(!full)
return (oldState & ~(END_MASK | FULL_BIT)) | (newEnd << END_SHIFT);
else
return (oldState & ~(END_MASK | FULL_BIT)) | (newEnd << END_SHIFT | FULL_BIT);
}
}
我没有衡量在激烈争用下的实际性能,因此您可能需要针对特定用例进行调整。我也建议您检查它是否包含种族(据我所知不包含种族)。