我的服务线程安全吗?

Is my service thread safe?

我有一个单例classMyService,它有两个函数读写数据from/to文件:

public class MyService {
 private static MyService instance;
 private MyFileAccessor fileAccessor;

 private MyService() {
   fileAccessor = new MyFileAccessor();
 }

 public static MyService getInstance() {
  if (instance == null) {
     instance = new MyService();
  }
  return instance;
 }
 // write data to file through fileAccessor object
 public void writeDataToFile(Object data){
   fileAccessor.writeToFile(data);
 }
 // read data from file through fileAccessor object
 public Object readFile() {
   return fileAccessor.readFromFile();
 }

}

MyFileAccessor class 具有 同步 函数来读取和写入文件:

public class MyFileAccessor {
  private File mFile;

  public MyFileAccessor() {
    mFile = new File(PATH);
  }
  public synchronized Object readFromFile() {
    // code to read mFile
    …
  } 

  public synchronized void writeToFile(Object data) {
    // code to write data to mFile
    …
  } 
}

我的问题:当我的项目通过它读写数据from/to文件时,单例MyServiceclass线程安全是吗?是否存在潜在的并发问题?

=====更新===

根据答案还有两个问题:

Q1. 我看到下面关于使用 Initialized-On-Demand idom 的答案。但是,如果我只在 getInstance() static 方法上使用 synchronized 关键字还不够吗?

public static synchronized MyService getInstance() {
 ...
}

它不也使单例实例创建成为原子的吗?

Q2.如果我只使用MyFileAccessorMyService实例,是否还需要将MyFileAccessor设为单例或同步MyFileAccessor.class ?我的意思是 MyService 是一个单例,它不是已经保证只有一个实例能够调用 MyFileAccessor 中的方法吗?

您现在实际上没有单身人士 class。因为您检查是否 instance == null 然后可能非原子地分配 instance = new MyService() ,所以两个线程有​​可能创建 MyService.

的实例

创建线程安全单例的一种方法是使用单元素枚举:

enum MyService {
  INSTANCE;

  // Rest of class body.
}

您现在可以通过 MyService.INSTANCE 获取您的实例。

另一种方法是 Initialization-on-demand idiom,它利用了 class 在第一次需要它之前不会初始化的事实:

class MyService {
  private static class Holder {
    private static final MyService INSTANCE = new MyService();
  }

  static MyService getInstance() {
    return Holder.INSTANCE;
  }
}

正如@Kayaman 在下面指出的那样,单例枚举模式是当前首选的实现方式。我可以想到您可能想要使用 IOD 习语的原因,例如,如果您需要扩展另一个 class(枚举不能扩展 classes,因为它们已经扩展了 Enum ; 但是,他们可以实现接口)。

为了完整起见,另一个模式是 Double-checked Locking,如果发现 instancenull:

,它使用同步块
static MyService getInstance() {
  if (instance == null) {
    synchronized (MyService.class) {
      if (instance == null) {
        instance = new MyService();
      }
    }
  }
  return instance;
}

完成后,您还应该制作所有字段final:对于构造函数完成执行,final 字段的分配有发生前保证。

private final MyFileAccessor fileAccessor; // In MyService.

private final File mFile;  // In MyFileAccessor.

否则,无法保证这些字段的值对所有线程都可见。


您还应该使 MyFileAccessor 成为单例(例如使用惰性持有者习惯用法),或者使方法在 MyFileAccessor.class 上同步,以确保只有一个实例能够调用这些方法同时