如何以线程安全的方式访问可变对象列表

时间:2013-06-15 06:37:46

标签: java thread-safety

假设我有以下类,其中包含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类中进行所有线程安全措施?

2 个答案:

答案 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(),这些线程仍可能保留它们收到的引用并在以后使用它。这会引入线程安全问题。

换句话说,即使他们在不同时间收到了引用,它仍然是相同的引用,可以在将来的时间由多个线程同时使用。