【问题标题】:Help reviewing the following code, is it thread safe?帮助查看以下代码,它是线程安全的吗?
【发布时间】:2010-10-30 08:47:51
【问题描述】:
private static Callback callback;

public Foo()
{
    super(getCallback());
}

private static Callback getCallback()
{
    callback = new Callback();
    return callback;
}

构造函数 Foo() 可能会从多个线程中调用。我关心的是私有静态字段“回调”和静态方法“getCallback()”。

可以看出,每次调用 'getCallback()' 时,它都会为静态字段 'callback' 分配一个新值。

我的 猜测 是它不是线程安全的,因为关键字 static 总是附加到类而不是实例上,这意味着静态字段“回调”一个 Foo 的值可能会被其他正在构建另一个 Foo() 的线程覆盖。这是正确的吗?

如果我错了,请纠正我。谢谢!

编辑:我的意图是在课堂上的某个地方保留“回调”,以便我以后可以重用它。但这并不容易,因为 Foo 从一个类扩展而来,该类具有强制传递“回调”的构造函数。

【问题讨论】:

  • 好问题。这些问题一个人很难从逻辑上思考。
  • 它无法编译。 actionCallback 来自哪里。作为一般规则,除非您想混淆,否则不要通过构造函数初始化静态字段。
  • 抱歉,'actionCallback' 应该是 'callback'。我的意图是在课堂上的某个地方保留“回调”,以便我以后可以重用它。但这并不容易,因为 Foo 从一个类扩展而来,该类具有强制传递“回调”的构造函数。

标签: java multithreading static-methods


【解决方案1】:

你想要每个线程一个回调,每个对象一个,还是一个真正的单例?

一些关于如何做不同变体的草图 - 只是从我的脑海中浮现出来,不要太从字面上理解:)

请注意,我假设 Callback 有一个不平凡的构造函数,它可能会抛出需要处理的异常,如果它是一个平凡的构造函数,您可以大大简化所有这些。

每个线程一个:

  private static ThreadLocal<Callback> callback;

  public Foo()
  {
      super(getCallback());
  }

  private static Callback getCallback()
  {
      if ( callback.get() == null ) 
          callback.set(new Callback());
      return callback.get();
  }

所有线程的单一回调:

  private final static Callback callback;

  static {
      callback = new Callback(); 
  }

  public Foo()
  {
      super(getCallback());
  }

  private static Callback getCallback()
  {
      return callback;
  }

并且,为了完整起见,每个对象一个回调:

  private Callback callback;

  public Foo()
  {
      super(getCallback());
  }

  private Callback getCallback()
  {
      callback = new Callback();
      return callback;
  }

【讨论】:

    【解决方案2】:

    它不是线程安全的。尝试以下替代方案:

    选项 1:这里所有实例共享相同的回调

    private static final Callback callback = new Callback();
    
    public Foo() {
        super(callback);
    }
    

    选项2:这里每个实例都有自己的回调

    public Foo() {
        super(new Callback());
    }
    

    注意,在这两种情况下,虽然构造函数是线程安全的,但是整个类的线程安全取决于Callback的实现。如果它具有可变状态,那么您将遇到潜在问题。如果 Callback 是不可变的,那么您就具有线程安全性。

    【讨论】:

      【解决方案3】:

      您正在尝试做的事情称为单例模式,如果您进行搜索,您会发现为什么如果可以的话避免这种模式通常是一个好主意,但是如果您需要它,您可以执行以下操作。

      private static final Callback CALLBACK= new Callback();
      

      或者如果你需要一个懒惰的单例,你可以这样做

      public class Foo {
         class CallbackHolder {
             static final Callback CALLBACK= new Callback();
         }
      
         public static Callback getCallback() {
            return CallbackHolder.CALLBACK;
         }
      
      public Foo() {
          super(getCallback());
      }
      

      两种实现都是线程安全的。

      【讨论】:

        【解决方案4】:

        我知道已经回答了,但原因并没有详细说明。

        如果有两个线程调用 getCallback() 方法,它们可以执行如下代码行:

        1. 线程 1 - 回调 = new Callback();
        2. 线程 2 - 回调 = new Callback();
        3. 线程 1 - 返回 actionCallback;
        4. 线程 2 - 返回 actionCallback;

        在这种情况下,在 (2.) 处生成的回调将在 (3.) 和 (4.) 中返回

        解决方案似乎是询问如果回调特定于实例而不是类,为什么静态定义回调。

        希望对你有帮助。

        【讨论】:

          【解决方案5】:

          我认为您自己已经完美地总结了它,但是如果没有更多关于您要实现的目标的详细信息,那么提出解决问题的建议将会很棘手。

          一个明显的问题是,callback 必须是静态的吗?或者您可以在不破坏类功能的情况下安全地将其设为实例字段吗?

          【讨论】:

            【解决方案6】:

            每次调用 Foo() 时(即使来自同一个线程),回调都会获得一个新值。我不太确定您的代码应该做什么(如果您只想初始化静态变量一次(单例),您应该检查它是否在 getCallback() 中仍然为空 - 以及 actionCallback 是什么?)。为了使其线程安全,请使用同步。

            【讨论】:

              【解决方案7】:

              是的,你是对的。当两个线程同时进入getCallback() 方法并且一个线程分配一个新的CallBack 给静态字段而另一个线程已经这样做但没有这样做时,Foo 的两个实例可能以相同的CallBack 实例结束还回来了。在这种情况下,最好的解决方法是不要使用静态字段,因为它没有任何用处。或者,使 getCallback() 同步。

              但请注意,只有static 关键字导致的代码不是线程安全的,这不是正确的。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-01-25
                • 2011-05-18
                相关资源
                最近更新 更多