【问题标题】:Is this Class thread safe? if not can someone explain a scenario?这个类线程安全吗?如果没有,有人可以解释一个场景吗?
【发布时间】:2015-05-13 02:30:51
【问题描述】:

我正在尝试了解有效不可变类的安全发布。对于我的班级,我无法想出线程不安全的场景。我需要添加一些其他的安全防护吗?

澄清:容器元素是线程安全的

 public class Container<E> {
    private LinkedList<E> data;

     public Container() {
        this.data = new LinkedList<E>();
    }

    public Container(final Container<E> other) {
        this.data = new LinkedList<E>(other.data);
    }

    public Container<E> add(E e) {
        Container<E> other_cont= new Container<E>(this);
        other_cont.data.add(e);
        return other_cont;
    }

    public Container<E> remove() {
        Container<E> other_cont= new Container<E>(this);
        other_cont.data.remove(0);
        return other_cont;
    }

     public E peek() {
        if(this.data.isEmpty())
            throw new NoSuchElementException("No element to peek at");
        return this.data.get(0);
    }

     public int size() {
        return this.data.size();
    }
 }

【问题讨论】:

  • 如果元素本身是线程不安全的,则可能存在问题。例如,假设您有一个 Container。理论上你可以调用 peek() 在两个单独的线程中检索相同的 LinkedList,然后你就有问题了。
  • 为了争论,假设元素本身是线程安全的。
  • 如果可以调用 add 或 remove 则不安全,并发调用可能会破坏双向链表。
  • @Ben 但是由于 add 和 remove 并没有修改列表本身,它怎么不安全?
  • 对不起,你是对的。我误读了。如果您使用写时复制,那么它是安全的

标签: java multithreading thread-safety safe-publication


【解决方案1】:

这看起来不错,只要 Container 没有不安全地发布(无论如何我们都不应该这样做)。

但是,为了便于讨论,假设一个 Container 对象是通过不安全发布传递的

 static Container shared; // not volatile

 // thread 1
 shared = containerX.add( e );

 // thread 2
 shared.peek();

这不是线程安全的;线程 2 可以观察到损坏状态。

要解决这个问题,我们需要final 变量;并且所有写入都应该在构造函数退出之前完成。在您的代码中,您在new Container 之后修改了other_cont,这就是问题所在。

就我个人而言,我会这样做

 public class Container<E> {
    final private LinkedList<E> data;

    public Container() {
        this.data = new LinkedList<E>();
    }

    Container(LinkedList<E> data) {
        this.data = data;
    }

    public Container<E> add(E e) {

        LinkedList<E> copy = new LinkedList<>(data); 
        copy.add( e );
        return new Container<>(copy);
    }

    ... etc

设计不可变类的经验法则 - 所有字段必须是final,并且所有写入都必须在构造函数退出之前完成。

【讨论】:

  • 我假设你的意思是“安全”;您能否详细说明您的答案,您是否建议对添加删除方法进行一些更改?
  • @Suryavanshi 哎呀,那句话我需要双重否定。
  • final in final private LinkedList&lt;E&gt; data; 只保证引用指向同一个LinkedList。它不会禁止列表的任何进一步变化
  • 只有Container类可以修改;并且 Container 类从不修改它。
  • "为什么要使用 final" - 好问题。 final 有两个独立的、不相关的语义; 1、表示字段不能改写;第二,它有一些记忆模型的语义。我们真的不需要这个班级的第一名,但我们确实需要第二名。
猜你喜欢
  • 2021-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 2013-01-09
  • 2012-04-19
相关资源
最近更新 更多