【问题标题】:What is the correct way to synchronize a shared, static object in Java?在 Java 中同步共享静态对象的正确方法是什么?
【发布时间】:2010-06-09 00:03:19
【问题描述】:

这是一个关于在 java 中同步共享对象的正确方法是什么的问题。一个警告是,我要共享的对象必须从静态方法访问。我的问题是,如果我在静态字段上同步,是否会锁定该字段所属的类,类似于同步静态方法的方式?或者,这只会锁定字段本身吗?

在我的具体示例中,我要问:调用 PayloadService.getPayload() 或 PayloadService.setPayload() 是否会锁定 PayloadService.payload?还是会锁定整个 PayloadService 类?

public class PayloadService extends Service {   


private static PayloadDTO payload = new PayloadDTO();


public static  void setPayload(PayloadDTO payload){
    synchronized(PayloadService.payload){
        PayloadService.payload = payload;
    }
}

public static  PayloadDTO getPayload() {
    synchronized(PayloadService.payload){
        return PayloadService.payload ;
    }
}

...

这是一种正确/可接受的方法吗?

在我的示例中,PayloadService 是一个单独的线程,定期更新有效负载对象 - 其他线程需要随机调用 PayloadService.getPayload() 以获取最新数据,我需要确保它们不会锁定 PayloadService 使其无法执行其计时器任务

根据回复,我重构如下:

public class PayloadHolder {

private static PayloadHolder holder;    
private static PayloadDTO payload;

private PayloadHolder(){        
}

public static synchronized PayloadHolder getInstance(){
    if(holder == null){
        holder = new PayloadHolder();
    }
    return holder;
}

public static synchronized void initPayload(){      
    PayloadHolder.payload = new PayloadDTO();       
}
public static synchronized PayloadDTO getPayload() {
    return payload;
}
public static synchronized void setPayload(PayloadDTO p) {
    PayloadHolder.payload = p;
}

}

public class PayloadService extends Service {   

  private static PayloadHolder payloadHolder = PayloadHolder.getInstance();

  public static  void initPayload(){        
            PayloadHolder.initPayload();        
  }

  public static  void setPayload(PayloadDTO payload){       
        PayloadHolder.setPayload(payload);      
  }

  public static  PayloadDTO getPayload() {      
    return PayloadHolder.getPayload();      
  }

     ...

这种方法合法吗?我也很好奇这样做更好还是使用 Hardcoded ... 提到的 AtomicReference 方法更好? - 我在 PayloadService 上保留一个 PayloadHolder 实例只是为了在 PayloadService 运行期间保持对 PayloadHolder 类的引用在 jvm 中处于活动状态。

【问题讨论】:

    标签: java multithreading synchronization


    【解决方案1】:

    您的代码应如下所示:

    public static  void setPayload(PayloadDTO payload){
        synchronized(PayloadService.class){
            PayloadService.payload = payload;
        }
    }
    
    public static  PayloadDTO getPayload() {
        synchronized(PayloadService.class){
            return PayloadService.payload ;
        }
    }
    

    即使方法不是静态的,您的原始代码也不会起作用。原因是您正在对正在更改的有效负载实例进行同步。

    更新,对 johnrock 评论的回应: 仅当您有其他要当前运行的同步静态块时,锁定整个类才是一个问题。如果你想拥有多个独立的锁定部分,那么我建议你这样做:

    public static final Object myLock = new Object();
    
    public static  void setPayload(PayloadDTO payload){
        synchronized(myLock){
            PayloadService.payload = payload;
        }
    }
    
    public static  PayloadDTO getPayload() {
        synchronized(myLock){
            return PayloadService.payload ;
        }
    }
    

    或者,如果您需要更复杂的并发模式,请查看 java.util.concurrent,它有许多预构建的类可以帮助您。

    【讨论】:

    • 这不就相当于写公共静态同步方法了吗?
    • 是的,但我保留了同步范围,以防他有更多不需要同步的代码。
    • 同步变化的实例有什么问题?
    • 好吧,我尝试这种方法并且没有使用静态方法的全部原因是我不想锁定整个类..而只是锁定对象。所以,你的建议做了我试图避免的事情。最终,我只是想找出同步一个线程更新和其他线程消耗的共享对象的正确方法。
    • 在非最终变量上同步是没有用的,因为引用可能会从你下面改变,你最终会得到多个你正在同步的对象,这违背了同步开始的整个目的与。
    【解决方案2】:

    如其他帖子中所述,您可以在班级或显式监视器上同步。

    如果我们假设您仅将 sychnronize 用于线程安全地获取和设置属性,还有 2 种其他方法:volatileAtomicReference

    易变

    volatile 关键字将对变量进行原子访问,这意味着读取和分配变量不会由 CPU 本地寄存器优化,而是原子完成。

    原子参考

    AtomicReference 是java.util.concurrent.atomic 包中的一个特殊类,它允许原子访问类似变量的引用。它与volatile 非常相似,但提供了一些额外的原子操作,例如 compareAndSet。

    例子:

    public class PayloadService extends Service {   
    
    private static final AtomicReference<PayloadDTO> payload 
              = new AtomicReference<PayloadDTO>(new PayloadDTO());
    
    public static void setPayload(PayloadDTO payload){
        PayloadService.payload.set(payload);
    }
    
    public static PayloadDTO getPayload() {
        return PayloadService.payload.get ;
    }
    

    编辑:

    您的 Holder 似乎很困惑,因为您实例化类只是为了调用静态方法。尝试使用 AtomicReference 修复它:

    public class PayloadHolder {
    
      private static AtomicReference<PayloadHolder> holder = new AtomicReference<PayloadHolder();    
    
      //This should be fetched through the holder instance, so no static
      private AtomicReference<PayloadDTO> payload = new AtomicReference<PayloadDTO>();
    
      private PayloadHolder(){        
      }
    
      public static PayloadHolder getInstance(){
        PayloadHolder instance = holder.get();
    
        //Check if there's already an instance
        if(instance == null){
    
          //Try to set a new PayloadHolder - if no one set it already.
          holder.compareAndSet(null, new PayloadHolder());
          instance = holder.get();
    
        }
        return instance;
      }
    
      public void initPayload(){      
        payload.set(new PayloadDTO());
    
        //Alternative to prevent a second init:
        //payload.compareAndSet(null, new PayloadDTO());
      }
    
      public PayloadDTO getPayload() {
        return payload.get;
      }
    
      public void setPayload(PayloadDTO p) {
        payload.set(p);
      }
    
    }
    
    public class PayloadService extends Service {   
    
      private final PayloadHolder payloadHolder = PayloadHolder.getInstance();
    
      public void initPayload(){        
        payloadHolder.initPayload();        
      }
    
      public void setPayload(PayloadDTO payload){       
        payloadHolder.setPayload(payload);      
      }
    
      public PayloadDTO getPayload() {      
        return payloadHolder.getPayload();      
      }
    }
    

    【讨论】:

    • 与我在原始问题中发布的重构示例相比,您建议的 AtomicReference 实现是否更受欢迎、性能更好?
    • 如果只设置一个变量,我肯定更喜欢 volatile/AtomicReference。没有锁,所以它的扩展性要好得多。 Volatile 和 AtomicReference 是通过使用原子 CPU 命令实现的,因此完全同步(刷新和更新寄存器、获取锁等)没有开销。
    • 感谢您的建议。我对您的方法 getInstance() 感到困惑。您在检查 if(holder == null) 之前调用 holder.get()。这是故意的吗?这会产生 NPE 吗?
    • 哎呀,对不起,应该是if (instance == null)。修复它...完成
    • 你的建议很棒。然而,在 PayloadService 上,我需要将 payloadHolder 字段以及 init、get 和 set 方法更改为静态,因为 PayloadService 是 UI 线程不包含其实例的后台线程。你觉得有味道吗?
    【解决方案3】:

    我的问题是,如果我在静态字段上进行同步,是否会像同步静态方法那样锁定该字段所属的类?或者,这只会锁定字段本身吗?

    不,它只是锁定对象本身(类属性而不是整个类)

    这是一种正确/可接受的方法吗?

    您可能可以看看java.util.concurrent.lock 包。

    我不太喜欢在类属性上进行同步,但我想这只是测试的问题。

    【讨论】:

    • 正确地说,它锁定了对象本身。我不确定说“它锁定了领域”是否足够清楚。
    • 我认为在类属性上同步的原因是需要调用 getPayload 的线程没有更新有效负载的 PayloadService 实例 - 它作为单独的线程运行。你有更好的建议吗?
    【解决方案4】:

    在另一个不变的静态对象上同步:

    public class PayloadService extends Service {   
    
    
    private static PayloadDTO payload = new PayloadDTO();
    
    private static final Object lock = new Object();
    
    
    public static  void setPayload(PayloadDTO payload){
        synchronized(lock){
            PayloadService.payload = payload;
        }
    }
    
    public static  PayloadDTO getPayload() {
        synchronized(lock){
            return PayloadService.payload ;
        }
    }
    

    【讨论】:

      【解决方案5】:

      这是一种正确/可接受的方法吗?

      不,这样做的原因是您永远不应该对可以更改其值的变量/字段进行同步。也就是说,当您在 PayloadService.payload 上进行同步并设置新的 PayloadService.payload 时,您就违反了同步的黄金法则。

      您应该在类实例上进行同步,或者创建一些任意的private static final Object lock = new Object() 并在其上进行同步。您将获得与同步类相同的效果。

      【讨论】:

      • 我想你想说“永远不要对可以改变其值的变量进行同步”。
      【解决方案6】:

      此类中的主要功能部分未发布,这有助于确定此方法是否是线程安全的:您如何从此类的其他部分访问 PayloadDTO 实例用过吗?

      如果您提供的方法可以在另一个实例中交换payload,而另一个线程正在运行使用payload 对象的代码,那么这不是线程安全的。

      例如,如果您有一个方法execute() 完成该类的主要工作并调用payload 上的方法,您需要确保一个线程不能使用setter 方法更改payload 实例,而另一个线程正忙于运行execute()

      简而言之,当你有共享状态时,你需要同步对状态的所有读写操作。

      就我个人而言,我不理解这种方法并且永远不会采用它 - 提供静态方法以允许其他线程重新配置一个类闻起来像是违反关注点分离。

      【讨论】:

      • 在我的示例中,我假设对有效负载的唯一访问是通过显示的 getPayload() 和 setPayload() 方法。但是,如果您鄙视这种方法,我很想听到.. 共享有效负载对象的正确方法是什么?我正在寻找处理此问题的正确方法,而不是与所述方法结合。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-30
      • 2015-05-22
      • 1970-01-01
      • 2013-08-06
      相关资源
      最近更新 更多