简体   繁体   English

使两个操作原子化

[英]Make two operations atomic

I'm implementing a custom datastructure that should follow these requirements: 我正在实现应遵循以下要求的自定义数据结构:

  1. Elements should be searchable (ie, locate and retrieve an element according to .equals() ) 元素应该是可搜索的(即根据.equals()定位和检索元素)
  2. When I try to retrieve an element and it is not found the method should block until such an element is available. 当我尝试检索一个元素但未找到该方法时,该方法应阻塞,直到此类元素可用为止。 If never, the method blocks forever or for a given timeout. 如果从不,该方法将永远阻塞或在给定的超时时间内阻塞。

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.

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