我有一个单身类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。如果我只使用MyFileAccessor
到MyService
个实例,是否仍需要将MyFileAccessor
设为单个或在MyFileAccessor.class上同步?我的意思是MyService是一个单例,是不是已经保证只有一个实例能够调用MyFileAccessor
中的方法?
答案 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,如果发现instance
为null
,则会使用同步块:
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
上同步,以确保只有一个实例能够在同一时间。