并发链表读取器/写入器无法正常工作

时间:2014-03-04 21:23:21

标签: java multithreading concurrency

我使用Java的ReadWriteLock实现了我自己的并发链表。我使用readwritelock编写了以下ConcurrentLinkedList类。然后我创建了一个读者类:ListReader和一个作家类:ListWriter。最后,我创建了一个编写器类和两个读取器类进行测试。

ConcurrentLinkedList类

public class ConcurrentLinkedList<T> {
    public static class Node<T> {
        T data;
        Node<T> next;

        public Node(T data) {
            this.data = data;
            this.next = null;
        }
    }

    private Node<T> head = null;

    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();

    public void add(T a) {
        writeLock.lock();
        try {
            Node<T> node = new Node<T>(a);
            Node<T> current = head;

            if (current == null) {
                current = node;
                head = current;
            } else {
                while (current.next != null) {
                    current = current.next;
                }
                current.next = node;
            }
        } finally {
            writeLock.unlock();
        }
    }

    public T get(int index) {
        readLock.lock();
        try {
            int i = 0;
            Node<T> current = head;

            while (current != null && i < index) {
                current = current.next;
                i++;
            }
            if (current == null)
                throw new IndexOutOfBoundsException();
            return current.data;
        } finally {
            readLock.unlock();
        }
    }

    int size() {
        readLock.lock();
        try {
            int size = 0;
            Node<T> current = head;
            while (current != null) {
                current = current.next;
                size++;
            }
            return size;
        } finally {
            readLock.unlock();
        }
    }
}

ListWriter类

public class ListWriter extends Thread {

    private ConcurrentLinkedList<Integer> list;
    private int[] arr;

    public ListWriter(ConcurrentLinkedList<Integer> list, int[] arr, String name) {
        this.list = list;
        this.arr = arr;
        setName(name);
    }

    @Override
    public void run() {
        for(int elem : arr) {
            list.add(elem);
            System.out.println("Thread " + getName() + " writing " + elem + " to the list");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

ListReader类

public class ListReader extends Thread {
    private ConcurrentLinkedList<Integer> list;

    public ListReader(ConcurrentLinkedList<Integer> list, String name) {
        setName(name);
        this.list = list;
    }

    @Override
    public void run() {
        for(int i=0; i<list.size(); i++) {
            int elem = list.get(i);
            System.out.println("Thread " + getName() + " reading " + elem + " from the list");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

MainRun课程

public class MainRun {
    public static void main(String[] args) {
        int[] numbers = new int[] { 1, 2, 3, 4, 5 };
        ConcurrentLinkedList<Integer> myList = new ConcurrentLinkedList<Integer>();

        Thread thread1 = new ListWriter(myList, numbers, "thread1");
        Thread thread2 = new ListReader(myList, "thread2");
        Thread thread3 = new ListReader(myList, "thread3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

然而,在运行程序后,有时我输出的输出不正确:

Thread thread1 writing 1 to the list
Thread thread3 reading 1 from the list
Thread thread1 writing 2 to the list
Thread thread3 reading 2 from the list
Thread thread1 writing 3 to the list
Thread thread1 writing 4 to the list
Thread thread3 reading 3 from the list
Thread thread1 writing 5 to the list
Thread thread3 reading 4 from the list
Thread thread3 reading 5 from the list

这意味着Reader thread2永远不会有机会运行。但有时它运行正常,其中thread2和thread3都从列表中读取。我甚至尝试将Reader线程更改为睡眠更长时间(例如Thread.sleep(500)中的ListReader),但是在thread2永远不会运行的情况下它仍然偶尔出错。什么原因导致读者线程的线程饥饿问题?为什么有时它会起作用,但有时它不起作用?

3 个答案:

答案 0 :(得分:2)

问题出在读者的迭代中:

for (int i = 0; i < list.size(); i++)
    list.get(i);

您可以同步size()get()。但请考虑这种情况:

reader              writer
--------            --------
size: 0
                    write
exit "for" loop
<get() not called>

你在读者面前开始写作,当然;但没有什么可以保证作者将在读者之前安排

你应该.sleep()稍长一些,但在检查列表大小之前这样做;另外,请考虑实施Iterable并使用它,这样可以避免.size()问题,尽管您仍然可以获得“短读”。

答案 1 :(得分:1)

您似乎想到的是队列而不是列表。否则您的循环已损坏,并且无法通过插入延迟来修复。您多次调用size()get(int),而不会防止可能发生的更改。在您的列表中添加remove(…)方法后,您将通过此类尝试进入真正的麻烦

如果你想要一个类似行为的队列,可以将ListReader的run方法更改为:

@Override
public void run() {
    try {
       while(list.size()==0) Thread.sleep(100);
    } catch (InterruptedException e) {
       e.printStackTrace();
    }
    for(int i=0; i<list.size(); i++) {
       int elem = list.get(i);
       System.out.println("Thread "+getName()+" reading "+elem+" from the list");
       try {
           while(i==list.size()) Thread.sleep(100);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
    }
}

但当然建议告诉读者预期的最大项目数以避免无限循环......

答案 2 :(得分:1)

这是ReadWriteLock实现的常见问题。在Java 5中,存在写入线程饥饿,然后在Java 6中,更新版本看到了读取线程饥饿。花时间阅读Kabutz's explnation

Java 8有一个解决方案!如果可以,请尝试安装Java 8并稍微重新编写测试以使用StampedLock。它利用乐观读取来防止读/写饥饿。