假设我有以下类,其中包含ElementClass
个对象的列表,并且ElementClass
是可变的:
public class ListContainer {
private List<ElementClass> elements = new ArrayList<ElementClass>();
public ListContainer(int n) {
for (int i = 0; i < n; ++i)
elements.add(new ElementClass());
}
public ElementClass getElement(String index) {
int i = Integer.parseInt(index);
if (i < elements.size())
return elements.get(i);
return null;
}
}
如何确保两个不同的线程无法获取对ElementClass
对象的相同引用并冒着意外行为的风险?可以通过更改ListContainer
类来完成,还是必须在ElementClass
类中进行所有线程安全措施?
答案 0 :(得分:1)
首先,你的类(如编写的)实际上是不可变的(从API的角度来看),因为没有任何操作允许在创建列表后修改列表。
(您可能可以对列表中的ElementClass
个实例执行变异操作。但这不会改变列表本身。)
如果您确实向ListContainer
添加了变异操作,则需要执行一些操作以使其具有线程安全性。但假设内部List
对象没有“泄漏”,则使相关的ListContainer
操作同步就足够了。
实际上,写的类不是100%线程安全的。一个线程可以创建一个实例,将引用传递给另一个线程,第二个线程可以在调用getElement
方法时查看实例列表状态的不一致版本。这是因为构造函数完成和后续方法调用之间没有发生在或同步顺序之间的关系。见JLS 17.4.4和17.4.5。
这可以通过多种方式“修复”。例如:
getElement
声明为synchronized
方法,elements
声明为volatile
或elements
声明为final
。最后一个因为最终字段的特殊规则而起作用;见JLS 17.5。 (如果ListContainer
旨在不可变,则将elements
声明为final
是最佳解决方案......)
如何确保两个不同线程无法获取对
ElementClass
对象的相同引用并冒着意外行为的风险?
这是可能的,但它需要一个额外的数据结构......跟踪哪些ElementClass
实例已被“检出”。而且你会遇到客户端没有“返回”实例,实现队列等问题。
可以通过改变
来完成ListContainer
类......
只是很困难;见上文。
...或者是否必须在
ElementClass
班级中采取所有线程安全措施?
这是最简单和最好的方法。如果您需要对操作序列进行互斥,则可以使相关(单个)操作同步,或设计用于锁定的“协议”。 (“协议”可能只是使用锁的“规则”...)
答案 1 :(得分:0)
需要在班级ElementClass
中采取安全措施。
即使您阻止多个线程同时调用getElement()
,这些线程仍可能保留它们收到的引用并在以后使用它。这会引入线程安全问题。
换句话说,即使他们在不同时间收到了引用,它仍然是相同的引用,可以在将来的时间由多个线程同时使用。