java - 单例类公共方法应该同步吗?
问题描述
我有一个为我的应用程序抽象弹性搜索 API 的单例包装类。
public class ElasticSearchClient {
private static volatile ElasticSearchClient elasticSearchClientInstance;
private static final Object lock = new Object();
private static elasticConfig ;
/*
** Private constructor to make this class singleton
*/
private ElasticSearchClient() {
}
/*
** This method does a lazy initialization and returns the singleton instance of ElasticSearchClient
*/
public static ElasticSearchClient getInstance() {
ElasticSearchClient elasticSearchClientInstanceToReturn = elasticSearchClientInstance;
if (elasticSearchClientInstanceToReturn == null) {
synchronized(lock) {
elasticSearchClientInstanceToReturn = elasticSearchClientInstance;
if (elasticSearchClientInstanceToReturn == null) {
// While this thread was waiting for the lock, another thread may have instantiated the clinet.
elasticSearchClientInstanceToReturn = new ElasticSearchClient();
elasticSearchClientInstance = elasticSearchClientInstanceToReturn;
}
}
}
return elasticSearchClientInstanceToReturn;
}
/*
** This method creates a new elastic index with the name as the paramater, if if does not already exists.
* Returns true if the index creation is successful, false otherwise.
*/
public boolean createElasticIndex(String index) {
if (checkIfElasticSearchIndexExists(index)) {
LOG.error("Cannot recreate already existing index: " + index);
return false;
}
if (elasticConfig == null || elasticConfig.equals(BatchConstants.EMPTY_STRING)) {
loadElasticConfigFromFile(ELASTIC_CONFIG_FILE_NAME);
}
if (elasticConfig != null && !elasticConfig.equals("")) {
try {
HttpURLConnection elasticSearchHttpURLConnection = performHttpRequest(
ELASTIC_SEARCH_URL + "/" + index,
"PUT",
elasticConfig,
"Create index: " + index
);
return elasticSearchHttpURLConnection != null &&
elasticSearchHttpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK;
} catch (Exception e) {
LOG.error("Unable to access Elastic Search API. Following exception occurred:\n" + e.getMessage());
}
} else {
LOG.error("Found empty config file");
}
return false;
}
private void loadElasticConfigFromFile(String filename) {
try {
Object obj = jsonParser.parse(new FileReader(filename);
JSONObject jsonObject = (JSONObject) obj;
LOG.info("Successfully parsed elastic config file: "+ filename);
elasticConfig = jsonObject.toString();
return;
} catch (Exception e) {
LOG.error("Cannot read elastic config from " + filename + "\n" + e.getMessage());
elasticConfig = "";
}
}
}
我有多个使用 ElasticSearchClient 的线程,如下所述
Thread1
ElasticSearchClient elasticSearchClient = ElasticSearchClient.getInstance()
elasticSearchClient.createElasticIndex("firstindex");
Thread2
ElasticSearchClient elasticSearchClient = ElasticSearchClient.getInstance()
elasticSearchClient.createElasticIndex("secondindex");
Thread3...
在我看来,单例类是线程安全的,但我不确定如果多个线程开始执行单例类的相同方法会发生什么。这有什么副作用吗?
注意:我知道上面的单例类不是反射和序列化安全的。
解决方案
在您的特定实施中
if (checkIfElasticSearchIndexExists(index)) { //NOT THREAD SAFE
LOG.error("Cannot recreate already existing index: " + index);
return false;
}
if (elasticConfig == null || elasticConfig.equals(BatchConstants.EMPTY_STRING)) { //NOT THREAD SAFE
loadElasticConfigFromFile(ELASTIC_CONFIG_FILE_NAME);
}
if (elasticConfig != null && !elasticConfig.equals("")) { //NOT THREAD SAFE
有 3 点可能导致赛车状况。
所以本身
单例类公共方法应该同步吗?
没有这样的规则——如果它们是线程安全的,那么就不需要额外的同步。在您的情况下,这些不是线程安全的,因此您必须使它们成为线程安全的,例如。制造
public synchronized boolean createElasticIndex
如果您关心并发写入单个索引,那么不要 - 这是一个 ElasticSearch 任务来正确处理并发写入(相信我 ES 会顺利处理)
什么不是线程安全的(指出 3 个地方)?具有并发 T1 和 T2:
checkIfElasticSearchIndexExists(index)
如果 T1 和 T2 将使用相同的索引名称,则两者都将通过此检查(我只假设这是一个休息调用——那是最糟糕的)elasticConfig == null || elasticConfig.equals(BatchConstants.EMPTY_STRING)
好吧,在第一次运行时,T1 和 T2 都将通过此测试,并且都将从文件中加载配置 - 可能不会产生影响,但它仍然是一个赛车条件if (elasticConfig != null && !elasticConfig.equals(""))
与 2 + 相同的场景,如果由于内存模型,如果 elasticConfig 不是volatile
它可以读取为“not null”之前loadElasticConfigFromFile
将完全初始化它。
2 和 3 可以通过双重检查锁定来修复(就像你做的那样,getInstance()
或者我宁愿将它移动到实例初始化块 - 我认为构造函数是最好的。
至于更好地理解这种现象,您可以检查为什么 a==1 && a==2 可以评估为真
然而,由于调用和响应之间的延迟,1 是更大的问题,您得到了一个宽窗口,其中 2 个线程可以查询相同的索引并获得完全相同的响应 - 该索引不存在并尝试创建一个。
推荐阅读
- java - 在测试带有消息“消息”的 Post API 时获取 415 状态代码:“不支持内容类型 'text/plain;charset=UTF-8'”
- python - 如何在 python 中使用 Weka DECORATE 元学习器进行合奏?
- android - 谷歌地图 - 制作类似抓取应用程序的动画以显示路径方向
- svm - 如何使用 2 个数据集,1 个用于训练,1 个用于在 WEKA 上进行情感分析测试
- c - 制作空白输出文件的程序,C
- php - 将数据放入php和curl中的更新表中
- javascript - Apps Script webapp 分阶段加载 Javascript (jQuery)
- gcc - 使用 gcc 的 -shared 和 -pie 选项时的问题和疑惑
- angular - 角度材质输入浮动标签默认颜色更改
- laravel-5 - 如何从 laravel 的表中更新软删除值