我定义了一个Element类:
class Element<T> {
T value;
Element<T> next;
Element(T value) {
this.value = value;
}
}
还定义了一个基于Element的List类。它是一个典型的列表,就像在任何数据结构书籍中一样,具有addHead,delete等操作
public class List<T> implements Iterable<T> {
private Element<T> head;
private Element<T> tail;
private long size;
public List() {
this.head = null;
this.tail = null;
this.size = 0;
}
public void insertHead (T node) {
Element<T> e = new Element<T>(node);
if (size == 0) {
head = e;
tail = e;
} else {
e.next = head;
head = e;
}
size++;
}
//Other method code omitted
}
如何使这个List类线程安全?
对所有方法进行同步?似乎不起作用。两个线程可能同时在不同的方法上工作并导致冲突。
如果我使用数组来保留类中的所有元素,那么我可以在数组上使用volatile来确保只有一个线程正在使用内部元素。但是目前所有元素都通过每个下一个指针上的对象引用链接。我无法使用volatile。
在头部,尾部和大小上使用挥发性物质?如果两个线程运行不同的方法保持资源彼此等待,这可能会导致死锁。
有什么建议吗?
答案 0 :(得分:3)
如果在每个方法上放置synchronized
,数据结构将是线程安全的。因为根据定义,一次只有一个线程将在对象上执行任何方法,并且还确保了线程间的排序和可见性。所以它就像一个线程正在进行所有操作一样好。
如果块覆盖的区域是整个方法,则放置synchronized(this)
块将没有任何不同。如果面积小于此值,您可能会获得更好的性能。
做类似
的事情private final Object LOCK = new Object();
public void method(){
synchronized(LOCK){
doStuff();
}
}
被认为是良好的做法,虽然不是为了更好的表现。这样做可以确保没有其他人可以使用您的锁,并且无意中创建了一个容易出现死锁的实现等。
在您的情况下,我认为您可以使用ReadWriteLock
来获得更好的读取性能。顾名思义,ReadWriteLock
允许多个线程通过,如果他们正在访问“读取方法”,这些方法不会改变对象的状态(当然,你必须正确识别你的哪个方法是“读取方法”) “和”写方法“,并相应地使用ReadWriteLock
!)。此外,它确保在执行“写入方法”时没有其他线程正在访问该对象。它负责读/写线程的调度。
使类线程安全的其他众所周知的方法是“CopyOnWrite”,您可以在突变时复制整个数据结构。仅当对象主要是“读取”并且很少“写入”时才建议这样做。
以下是该策略的示例实现。 http://www.codase.com/search/smart?join=class+java.util.concurrent.CopyOnWriteArrayList
private volatile transient E[] array;
/**
* Returns the element at the specified position in this list.
*
* @param index index of element to return.
* @return the element at the specified position in this list.
* @throws IndexOutOfBoundsException if index is out of range <tt>(index
* < 0 || index >= size())</tt>.
*/
public E get(int index) {
E[] elementData = array();
rangeCheck(index, elementData.length);
return elementData[index];
}
/**
* Appends the specified element to the end of this list.
*
* @param element element to be appended to this list.
* @return true (as per the general contract of Collection.add).
*/
public synchronized boolean add(E element) {
int len = array.length;
E[] newArray = (E[]) new Object[len+1];
System.arraycopy(array, 0, newArray, 0, len);
newArray[len] = element;
array = newArray;
return true;
}
这里,read方法是在不经过任何锁定的情况下访问,而write方法必须是synchronized
。通过对数组使用volatile
来确保读取方法的线程间排序和可见性。
写入方法必须“复制”的原因是因为赋值array = newArray
必须是“一次性”(在java中,对象引用的赋值是原子的),并且您可能不会触及原始数组操纵。
答案 1 :(得分:1)
我会查看java.util.LinkedList类的源代码以获得真正的实现。
默认情况下,同步会锁定类的实例 - 这可能不是您想要的。 (特别是如果Element可从外部访问)。如果你在同一个锁上同步所有方法,那么你将有可怕的并发性能,但它会阻止它们同时执行 - 实际上是对类的单线程访问。
另外 - 我看到一个尾部参考,但是没有看到带有相应前一个字段的Element,对于双链表 - 原因?
答案 2 :(得分:1)
我建议您使用可以传递给列表中每个元素的ReentrantLock,但是您必须使用工厂来实例化每个元素。
任何时候你需要从列表中取出一些东西,你将阻止同一个锁,这样你就可以确保没有两个线程同时访问。