我的服务线程安全吗?
Is my service thread safe?
我有一个单例classMyService
,它有两个函数读写数据from/to文件:
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
class 具有 同步 函数来读取和写入文件:
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
…
}
}
我的问题:当我的项目通过它读写数据from/to文件时,单例MyService
class线程安全是吗?是否存在潜在的并发问题?
=====更新===
根据答案还有两个问题:
Q1. 我看到下面关于使用 Initialized-On-Demand idom 的答案。但是,如果我只在 getInstance()
static 方法上使用 synchronized
关键字还不够吗?
public static synchronized MyService getInstance() {
...
}
它不也使单例实例创建成为原子的吗?
Q2.如果我只使用MyFileAccessor
到MyService
实例,是否还需要将MyFileAccessor
设为单例或同步MyFileAccessor.class ?我的意思是 MyService 是一个单例,它不是已经保证只有一个实例能够调用 MyFileAccessor
中的方法吗?
您现在实际上没有单身人士 class。因为您检查是否 instance == null
然后可能非原子地分配 instance = new MyService()
,所以两个线程有可能创建 MyService
.
的实例
创建线程安全单例的一种方法是使用单元素枚举:
enum MyService {
INSTANCE;
// Rest of class body.
}
您现在可以通过 MyService.INSTANCE
获取您的实例。
另一种方法是 Initialization-on-demand idiom,它利用了 class 在第一次需要它之前不会初始化的事实:
class MyService {
private static class Holder {
private static final MyService INSTANCE = new MyService();
}
static MyService getInstance() {
return Holder.INSTANCE;
}
}
正如@Kayaman 在下面指出的那样,单例枚举模式是当前首选的实现方式。我可以想到您可能想要使用 IOD 习语的原因,例如,如果您需要扩展另一个 class(枚举不能扩展 classes,因为它们已经扩展了 Enum
; 但是,他们可以实现接口)。
为了完整起见,另一个模式是 Double-checked Locking,如果发现 instance
是 null
:
,它使用同步块
static MyService getInstance() {
if (instance == null) {
synchronized (MyService.class) {
if (instance == null) {
instance = new MyService();
}
}
}
return instance;
}
完成后,您还应该制作所有字段final
:对于构造函数完成执行,final 字段的分配有发生前保证。
private final MyFileAccessor fileAccessor; // In MyService.
private final File mFile; // In MyFileAccessor.
否则,无法保证这些字段的值对所有线程都可见。
您还应该使 MyFileAccessor
成为单例(例如使用惰性持有者习惯用法),或者使方法在 MyFileAccessor.class
上同步,以确保只有一个实例能够调用这些方法同时
我有一个单例classMyService
,它有两个函数读写数据from/to文件:
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
class 具有 同步 函数来读取和写入文件:
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
…
}
}
我的问题:当我的项目通过它读写数据from/to文件时,单例MyService
class线程安全是吗?是否存在潜在的并发问题?
=====更新===
根据答案还有两个问题:
Q1. 我看到下面关于使用 Initialized-On-Demand idom 的答案。但是,如果我只在 getInstance()
static 方法上使用 synchronized
关键字还不够吗?
public static synchronized MyService getInstance() {
...
}
它不也使单例实例创建成为原子的吗?
Q2.如果我只使用MyFileAccessor
到MyService
实例,是否还需要将MyFileAccessor
设为单例或同步MyFileAccessor.class ?我的意思是 MyService 是一个单例,它不是已经保证只有一个实例能够调用 MyFileAccessor
中的方法吗?
您现在实际上没有单身人士 class。因为您检查是否 instance == null
然后可能非原子地分配 instance = new MyService()
,所以两个线程有可能创建 MyService
.
创建线程安全单例的一种方法是使用单元素枚举:
enum MyService {
INSTANCE;
// Rest of class body.
}
您现在可以通过 MyService.INSTANCE
获取您的实例。
另一种方法是 Initialization-on-demand idiom,它利用了 class 在第一次需要它之前不会初始化的事实:
class MyService {
private static class Holder {
private static final MyService INSTANCE = new MyService();
}
static MyService getInstance() {
return Holder.INSTANCE;
}
}
正如@Kayaman 在下面指出的那样,单例枚举模式是当前首选的实现方式。我可以想到您可能想要使用 IOD 习语的原因,例如,如果您需要扩展另一个 class(枚举不能扩展 classes,因为它们已经扩展了 Enum
; 但是,他们可以实现接口)。
为了完整起见,另一个模式是 Double-checked Locking,如果发现 instance
是 null
:
static MyService getInstance() {
if (instance == null) {
synchronized (MyService.class) {
if (instance == null) {
instance = new MyService();
}
}
}
return instance;
}
完成后,您还应该制作所有字段final
:对于构造函数完成执行,final 字段的分配有发生前保证。
private final MyFileAccessor fileAccessor; // In MyService.
private final File mFile; // In MyFileAccessor.
否则,无法保证这些字段的值对所有线程都可见。
您还应该使 MyFileAccessor
成为单例(例如使用惰性持有者习惯用法),或者使方法在 MyFileAccessor.class
上同步,以确保只有一个实例能够调用这些方法同时