Reputation: 13
I'm trying to create thread safe queue with unique elements. I see no vulnerabilities but not sure still is this realisation thread safe or not? get(), add(), toArray() methods do everything under lock, toString() and equals() use toArray() in order to get main array copy and work independently with copy without locks.
public class SafeQueue<T> {
private final Object[] queue;
private final HashSet<T> content;
private final Lock lock;
private int size;
public SafeQueue() {
queue = new Object[100];
content = new HashSet<>(100);
lock = new ReentrantLock();
}
public boolean add(T el) {
Objects.requireNonNull(el);
final Lock lock = this.lock;
lock.lock();
try {
//some logic
} finally {
lock.unlock();
}
return true;
}
public T get() {
final Lock lock = this.lock;
lock.lock();
try {
T el = (T) queue[0];
if (el != null) {
//some shift logic
content.remove(el);
}
return el;
} finally {
lock.unlock();
}
}
public Object[] toArray() {
final Lock lock = this.lock;
lock.lock();
try {
return Arrays.copyOf(this.queue, size);
} finally {
lock.unlock();
}
}
@Override
public boolean equals(Object o) {
Object[] eqQueue = ((SafeQueue<?>) o).toArray();
Object[] curQueue = toArray();
//some comparison logic with eqQueue and curQueue
return equal;
}
@Override
public String toString() {
Object[] curQueue = toArray();
StringBuilder sb = new StringBuilder();
sb.append('[');
//some logic with curQueue
return sb.toString();
}
}
Upvotes: 0
Views: 220
Reputation: 27115
You might want to ask, what does it mean for the equals
method to be thread safe? Consider this:
SafeQueue<T> qa = ...;
SafeQueue<T> qb = ...;
...
if (qa.equals(qb)) {
handleEqualsCase();
}
If you had any reason to worry about the thread-safety of the equals()
method, that could only be because other threads potentially could modify either queue when equals()
is called.
But, by the time handleEqualsCase()
is called, those other threads still could be running, and now, neither qa
nor qb
is locked. There is no guarantee that the two queues still will be equal when handleEqualsCase()
is called. But, if they're not equal, that must be a Bad Thing, right? Otherwise, why would you have bothered to test for equality in the first place?
Here's one way around that problem. Instead of writing a traditional equals()
method, write something like this instead:
private static
boolean unsynchronizedEqualityTest(SafeQueue<T> qa, SafeQueue<T> qb) {
...
}
public static
void doIfEqual(SafeQueue<T> qa, SafeQueue<T> qb, Runnable handler) {
qa.lock.lock();
qb.lock.lock();
try {
if (unsynchronizedEqualityTest(qa, qb)) {
handler.run();
}
} finally {
qb.lock.unlock();
qa.lock.unlock();
}
}
Now, when the client-supplied handler
is invoked, it's guaranteed that the two queues still will be equal because they're both still locked.
But BEWARE! There's potential for a deadlock if one thread calls doIfEqual(qa,qb,...)
and another thread calls doIfEqual(qb,qa,...)
. I'll leave it to you to figure out how to prevent that deadlock from happening.
Upvotes: 2