[英]Make two operations atomic
I'm implementing a custom datastructure that should follow these requirements: 我正在实现应遵循以下要求的自定义数据结构:
.equals()
) .equals()
定位和检索元素) I have implemented my own class - I started modifying LinkedBlockingQueue first - but I have a point where I can have a bad interleaving and I can't figure out how to avoid one. 我已经实现了自己的类-我首先开始修改LinkedBlockingQueue-但我有一个交织不好的地方,我不知道该如何避免。
To test the datastructure I created a method with 3 threads. 为了测试数据结构,我创建了一个具有3个线程的方法。 Each of these threads will try and receive a unique string (eg, "Thread1" for the first thread etc..).
这些线程中的每个线程都将尝试接收唯一的字符串(例如,第一个线程的“ Thread1”等)。 If the string is found in the datastore, it will insert a string for the next thread (eg, Thread 1 will insert "Thread2").
如果在数据存储区中找到该字符串,它将为下一个线程插入一个字符串(例如,线程1将插入“ Thread2”)。
This way, a thread will block and only send a message if it's message is in the store. 这样,线程将阻止并且仅在消息位于存储区中时才发送消息。 To start things off, after I started my threads, I manually add "Thread1" to the store.
首先,在启动线程后,我将“ Thread1”手动添加到存储中。 This should trigger Thread 1 to get his value out of the store and insert a value for thread 2. Thread 2 should then be notified and get his value and insert a value for thread 3 etc..
这将触发线程1从存储中获取其值并为线程2插入一个值。然后应通知线程2,并为其获取值并为线程3插入一个值,以此类推。
However, I notice that the cycle stops after a random times of passing the message around. 但是,我注意到,在随机传递消息后,周期停止了。 This is - I think - due to a bad interleaving that is possible in the
takeOrWait()
method (as indicated there). -我认为-这是由于
takeOrWait()
方法中可能存在不良的交错(如此处所示)。 I have tried several solutions but I can't figure out a way. 我已经尝试了几种解决方案,但找不到办法。
The problem is - I think - that I have to release the lock to modify, followed by a call to sync.wait()
. 问题是- 我认为 -我必须释放锁进行修改,然后再调用
sync.wait()
。 In between those calls a thread can already insert an element in the queue which would cause all the waiting threads to miss the notification. 在这两次调用之间,线程可以在队列中插入一个元素,这将导致所有正在等待的线程错过通知。
Is it possible to initiate the wait()
before I release the lock? 我可以在释放锁之前启动
wait()
吗?
For completness' sake I have added the Main.java class I use to test. 为了完善起见,我添加了用于测试的Main.java类。
BlockingDataStore 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 ** ** 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: ");
}
}
The problem is - I think - that I have to release the lock to modify, followed by a call to sync.wait()
问题是-我认为-我必须释放锁进行修改,然后再调用sync.wait()
Sounds like lost notification . 听起来像是丢失了通知 。 Understand that
sync.notify()
does nothing at all if there is not some other thread already blocked in sync.wait()
. 记者了解到,
sync.notify()
做什么都没有,如果没有已阻塞其他线程sync.wait()
The sync
object does not remember that it was notified. sync
对象不记得已收到通知。
This doesn't work (based on your example): 这不起作用(根据您的示例):
public void waitForFlag() {
...
while (! flag) {
synchronized (sync) {
try {
sync.wait();
} catch (InterruptedException e) { ... }
}
}
}
public void setFlag() {
flag = true;
synchronized (sync) {
sync.notifyAll();
}
}
Suppose thread A calls waitForFlag(), finds flag to be false, and then is preempted. 假设线程A调用waitForFlag(),发现标志为false,然后被抢占。 Then, thread B calls setFlag(), notifying no-one.
然后,线程B调用setFlag(),通知没有一个。 Finally, thread A calls sync.wait().
最后,线程A调用sync.wait()。
Now you have the flag set, and thread A blocked in a wait() call, waiting for somebody to set the flag. 现在,您已经设置了标志,并且线程A在wait()调用中被阻塞,等待有人设置标志。 That's what a lost notification looks like.
这就是丢失的通知的样子。
Here's how it should look: 外观如下:
public void waitForFlag() {
...
synchronized(sync) {
while (! flag) {
try {
sync.wait();
} catch (InterruptedException e) { ... }
}
}
}
public void setFlag() {
synchronized (sync) {
flag = true;
sync.notifyAll();
}
}
This way, the notification can not be lost because the statement that sets the flag and the statement that tests the flag are both inside synchronized blocks. 这样,通知不会丢失,因为设置标志的语句和测试标志的语句都在同步块内。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.