我有一个单例类MyService,它具有两个功能,用于从文件读取数据/向文件写入数据:

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类具有读取和写入文件的同步功能:

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


我的问题:当我的项目通过它读写数据时,单例MyService类是否是线程安全的?是否存在潜在的并发问题?

=====更新===

根据答案还有两个问题:

Q1。我看到以下有关使用按需初始化idom的答案。但是,仅在synchronized静态方法上使用getInstance()关键字还不够吗?

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


难道还使单例实例的创建原子化了吗?

Q2。如果仅通过MyFileAccessor实例使用MyService,是否还需要使MyFileAccessor单例或在MyFileAccessor.class上同步?我的意思是MyService是单例,不是已经保证只有一个实例能够调用MyFileAccessor中的方法吗?

最佳答案

您目前实际上没有单例课程。因为您检查是否instance == null,然后可能非原子地分配instance = new MyService(),所以两个线程可能会创建MyService实例。

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

enum MyService {
  INSTANCE;

  // Rest of class body.
}


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

另一种选择是Initialization-on-demand idiom,它利用了一个类,直到第一次需要它才被初始化的事实:

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

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


如下@Kayaman所述,单例枚举模式是当前首选的实现方式。我可以考虑为什么要使用IOD习惯用法的原因,例如,如果需要扩展另一个类(枚举不能扩展类,因为它们已经扩展了Enum;但是,它们可以实现接口)。

为了完整起见,另一个模式是Double-checked Locking,如果发现instancenull,则使用同步块:

static MyService getInstance() {
  if (instance == null) {
    synchronized (MyService.class) {
      if (instance == null) {
        instance = new MyService();
      }
    }
  }
  return instance;
}




完成此操作后,还应该使所有字段都为final:关于构造函数完成执行的最终字段分配,有一个事前保证。

private final MyFileAccessor fileAccessor; // In MyService.

private final File mFile;  // In MyFileAccessor.


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



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

09-11 05:49