我正在实施应遵循以下要求的自定义数据结构:
.equals()
定位和检索元素)我已经实现了自己的类 - 我首先开始修改LinkedBlockingQueue - 但是我有一个问题,我可能会有一个错误的交错,我无法弄清楚如何避免一个。
为了测试数据结构,我创建了一个包含3个线程的方法。这些线程中的每一个都将尝试接收唯一的字符串(例如," Thread1"用于第一个线程等...)。如果在数据存储区中找到该字符串,它将为下一个线程插入一个字符串(例如,线程1将插入"线程2")。
这样,一个线程就会阻塞,只有当它的消息在商店中时才会发送消息。为了开始,在我开始我的线程之后,我手动添加" Thread1"到商店。这应该触发线程1从存储中获取其值并为线程2插入值。然后应该通知线程2并获取其值并为线程3等插入值。
但是,我注意到在循环传递随机时间后循环停止。这是 - 我认为 - 由于takeOrWait()
方法中可能出现的错误交错(如此处所示)。我尝试了几种解决方案,但我找不到办法。
问题是 - 我认为 - 我必须释放锁以进行修改,然后调用sync.wait()
。在这些调用之间,线程已经可以在队列中插入一个元素,这会导致所有等待的线程错过通知。
在我解除锁定之前是否可以启动wait()
?
for completness'我已经添加了我用来测试的Main.java类。
BlockingDataStore
public class BlockingStore<T> {
// Linked list node.
static class Node<E> {
/**
* The item, volatile to ensure barrier separating write and read
*/
volatile E item;
Node<E> next;
Node(E x) {
item = x;
}
}
private Node<T> _head;
private Node<T> _lastPtr;
private int _size;
// Locks
private Lock changeLock = new ReentrantLock();
private final Object sync = new Object();
//////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////
public BlockingStore() {
_head = null;
_lastPtr = null;
}
//////////////////////////////////
// INTERNAL MODIFICATION //
//////////////////////////////////
/**
* Locates an element in the storage and removes it.
* Returns null if the element is not found in the list.
*
* @param toRemove Element to remove.
* @return Returns the removed element.
*/
private T findAndRemove(T toRemove) {
T result = null;
// Empty queue.
if (_head == null)
return result;
// Do we want the head?
if (_head.item.equals(toRemove)) {
result = _head.item;
_head = _head.next;
this._size--;
return result;
}
Node<T> previous = _head;
Node<T> current = previous.next;
while (current != null) {
if (current.item.equals(toRemove)) {
// Take the element out of the list.
result = current.item;
// we have to update the last pointer.
if (current == _lastPtr)
_lastPtr = previous.next;
else
previous.next = current.next;
this._size--;
return result;
}
previous = current;
current = current.next;
}
return result;
}
/**
* Adds an element to the end of the list.
*
* @param toAdd Element to add to the end of the list.
*/
private void addToEnd(T toAdd) {
// If the queue is empty
if (_head == null) {
_head = new Node<T>(toAdd);
_lastPtr = _head;
} else {
_lastPtr.next = new Node<T>(toAdd);
_lastPtr = _lastPtr.next;
}
this._size++;
}
/**
* Takes an element from the front of the list.
* Returns null if list is empty.
*
* @return Element taken from the front of the list.
*/
private T takeFromFront() {
// Check for empty queue.
if (_head == null)
return null;
T result = _head.item;
_head = _head.next;
this._size--;
return result;
}
//////////////////////////////////
// API METHODS //
//////////////////////////////////
/**
* Takes an element from the datastore,
* if it is not found the method blocks
* and retries every time a new object
* is inserted into the store.
*
* @param toTake
* @return
*/
public T takeOrWait(T toTake) {
T value;
changeLock.lock();
value = findAndRemove(toTake);
// Wait until somebody adds to the store
// and then try again.
while (value == null)
// Sync on the notification object
// such that we are waken up when there
// is a new element.
synchronized (sync) {
changeLock.unlock(); // allow writes.
// I think I can have bad inter-leavings here.
// If an insert is interleaved here, the thread
// will never be notified..
try {
sync.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
changeLock.lock();
value = findAndRemove(toTake);
}
changeLock.unlock();
return value;
}
public T dequeue() {
T value;
changeLock.lock();
value = takeFromFront();
changeLock.unlock();
return value;
}
public void enqueue(T toPut) {
changeLock.lock();
addToEnd(toPut);
// Notify about new element in queue.
synchronized (sync) {
sync.notifyAll();
changeLock.unlock();
}
}
public int size() {
return _size;
}
}
** Main.java **
public class Main {
public static void main(String[] args) throws InterruptedException {
final BlockingStore<String> q = new BlockingStore<String>();
new Thread(new Runnable() {
public String name = "Thread 1: ";
public String to = "Thread 2: ";
public void print(String message) {
System.out.println(name + message);
}
@Override
public void run() {
while (true) {
String value = q.takeOrWait(name);
print("Got: " + value);
q.enqueue(to);
}
}
}).start();
new Thread(new Runnable() {
public String name = "Thread 2: ";
public String to = "Thread 3: ";
public void print(String message) {
System.out.println(name + message);
}
@Override
public void run() {
while (true) {
String value = q.takeOrWait(name);
print("Got: " + value);
q.enqueue(to);
}
}
}).start();
new Thread(new Runnable() {
public String name = "Thread 3: ";
public String to = "Thread 1: ";
public void print(String message) {
System.out.println(name + message);
}
@Override
public void run() {
while (true) {
String value = q.takeOrWait(name);
print("Got: " + value);
q.enqueue(to);
}
}
}).start();
Thread.sleep(1000);
System.out.println("Main: Sending new message to queue for thread 1");
q.enqueue("Thread 1: ");
}
}
答案 0 :(得分:1)
问题是 - 我认为 - 我必须释放锁以进行修改,然后调用sync.wait()
听起来像丢失通知。如果sync.notify()
中没有其他线程已被阻止,请理解sync.wait()
什么都不做。 sync
对象不记得已通知。
这不起作用(基于您的示例):
public void waitForFlag() {
...
while (! flag) {
synchronized (sync) {
try {
sync.wait();
} catch (InterruptedException e) { ... }
}
}
}
public void setFlag() {
flag = true;
synchronized (sync) {
sync.notifyAll();
}
}
假设线程A调用waitForFlag(),发现标志为false,然后被抢占。然后,线程B调用setFlag(),通知no-one。最后,线程A调用sync.wait()。
现在你设置了标志,并且在wait()调用中阻塞了线程A,等待有人设置标志。这就是丢失通知的样子。
以下是它的外观:
public void waitForFlag() {
...
synchronized(sync) {
while (! flag) {
try {
sync.wait();
} catch (InterruptedException e) { ... }
}
}
}
public void setFlag() {
synchronized (sync) {
flag = true;
sync.notifyAll();
}
}
这样,通知不会丢失,因为设置标志的语句和测试标志的语句都是在同步块内。