【问题标题】:Is my service thread safe?我的服务线程安全吗?
【发布时间】:2015-12-22 13:28:52
【问题描述】:

我有一个 singletonMyService,它有两个函数可以从/向文件读取和写入数据:

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 的答案。但是如果我只在getInstance() static 方法上使用synchronized 关键字还不够吗?

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

不是也让单例实例创建原子化了吗?

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

【问题讨论】:

  • 这不是一个单例,因为您对instance == null 的检查并可能分配instance = new MyService() 不是原子完成的。查看Initialization-on-demand idiom
  • @AndyTurner,你的意思是当多个线程调用MyService.getInstance()时,它不是线程安全的吗?如果是这样,如何使其安全?
  • @Leem.fin 是的。 “查看按需初始化习语。”。
  • 你应该将mFile设为final,以保证它对所有线程的可见性。

标签: java multithreading java-threads


【解决方案1】:

您目前实际上没有单例类。因为你检查了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:对于构造函数完成执行的最终字段分配有一个happens-before保证。

private final MyFileAccessor fileAccessor; // In MyService.

private final File mFile;  // In MyFileAccessor.

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


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

【讨论】:

  • 那么房间里有大象 - FileAccessor 线程安全吗?是的,它是synchronized,但它不是一个单例,它看起来总是打开同一个文件,所以创建多个文件,并且您同时读取和写入同一个文件。
  • 双重检查锁定确实有效。几年前,在 Java 1.5 中“修复”内存模型之前,它不起作用。我会使用Enum singleton 的当前标准最佳实践而不是Holder,它简单、懒惰并且保证可以工作。
  • @OldCurmudgeon 完全有效的大型游戏狩猎,已更新。
  • @Andy Turner,谢谢,但根据您的回答,我还有一个问题。我很想知道我是否只使用 MyFileAccessorMyService, 是否仍然需要使 MyFileAccessor 成为单例或在 MyFileAccessor.class 上同步?我的意思是MyService 是一个单例,不是已经保证只有一个实例能够调用MyFileAccessor 中的方法吗?
  • 请根据您的回答查看我更新的问题。谢谢。
猜你喜欢
  • 2012-01-03
  • 1970-01-01
  • 1970-01-01
  • 2018-02-14
  • 2014-02-05
  • 2010-10-15
  • 1970-01-01
  • 1970-01-01
  • 2015-11-03
相关资源
最近更新 更多