我的服务线程安全吗?

时间:2015-12-22 13:28:52

标签: java multithreading java-threads

我有一个单身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。我在下面看到有关使用Initialized-On-Demand idom的答案。但是,如果我只是在synchronized 静态方法上使用getInstance()关键字,那还不够吗?

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

它是否也使单例实例创建原子?

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

1 个答案:

答案 0 :(得分:3)

目前你实际上没有单身人士课程。因为您检查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上同步,以确保只有一个实例能够在同一时间。