我有一个单例类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,如果发现
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
上同步,以确保只有一个实例能够同时调用这些方法。