在Java中同步共享静态对象的正确方法是什么?

时间:2010-06-09 00:03:20

标签: java multithreading synchronization

这是一个关于在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();      
  }

     ...

这种方法合法吗?我也很好奇这样做或使用硬编码提到的AtomicReference方法更好吗? - 我在PayloadService上保留一个PayloadHolder实例,只要在PayloadService运行的时候保持对jvm中活动的PayloadHolder类的引用。

6 个答案:

答案 0 :(得分:3)

您的代码应如下所示:

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,它有许多预先构建的类来帮助您。

答案 1 :(得分:1)

  

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

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

  

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

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

我真的不喜欢在类属性上进行同步,但我想这只是一个问题。

答案 2 :(得分:1)

  

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

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

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

答案 3 :(得分:1)

同步另一个不会改变的静态对象:

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 ;
    }
}

答案 4 :(得分:1)

如其他帖子中所述,您可以在课堂上或在显式监视器上进行同步。

还有其他两种方法,如果我们假设您只使用sychnronize进行线程安全获取和设置属性:volatileAtomicReference

<强>易失性

volatile关键字将访问变量atomic,这意味着读取和分配变量不会被CPU本地寄存器优化并以原子方式完成。

<强>的AtomicReference

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 ;
}

修改

你的持有人似乎很困惑,因为你只是为了调用静态方法而实例化类。尝试使用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();      
  }
}

答案 5 :(得分:0)

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

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

例如,如果您有一个方法execute()来执行此类的主要工作并调用payload上的方法,则需要确保一个线程无法更改payload实例使用setter方法,而另一个线程忙于运行execute()

简而言之,当您具有共享状态时,您需要在状态上对所有读取和写入操作进行同步。

就我个人而言,我不理解这种方法,并且永远不会接受它 - 提供静态方法来允许其他线程重新配置一个类似于违反关注点分离的类。