我使用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永远不会运行的情况下它仍然偶尔出错。什么原因导致读者线程的线程饥饿问题?为什么有时它会起作用,但有时它不起作用?
答案 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。它利用乐观读取来防止读/写饥饿。