java 单例模式
单例模式是设计模式中经常用到的一种,我最近看了下,再此记录下自己的学习成果,以供大家参考借鉴。
1. 定义:
确保一个类只有一个实例,并提供一个全局访问点。
2. 设计思路剖析
我们如果把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行实例产生实例,所以要把这个类的构造方法声明为私有,只能通过这个类实例化,想要获取这个类的实例,只能通过单间类,我们需要对全局提供这个实例的访问点,当你像类查询时,返回单个实例。
3. 经典单例模式实现
public class Singleton {
//利用静态变量来记录Singleton类的唯一实例
private static Singleton uniqueInstance;
//把构造器声明为私有,确保只有自身Singleton类内才可以调用构造器。
private Singleton() {
}
//用getInstance()方法实例化对象,并返回这个实例
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
//当需要Singleton实例时,uniqueInstance为空,我们则利用私有的构造器,产生一个Singleton实例,并赋值到uniqueInstance静态变量中。
//当我们不需要时,它就永远不会产生,这就是"延迟实例化"
}
return uniqueInstance;
}
}
4. 使用场景
①. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
②. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
③. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
④. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
⑤. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
⑥. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
⑦. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
⑧. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
⑨. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
⑩. 结巴分词的WordDictionary类
public class WordDictionary {
private static WordDictionary singleton;
private static final String MAIN_DICT = "/dict.txt";
private static final String USER_DICT = "/userDict.txt";
private static final String USER_STOP_DICT = "/userStopDict.txt";
private static String USER_DICT_SUFFIX = ".dict";
//停用词频次数组
private static Map<String, Double> stopWordFreqs = new HashMap<String, Double>();
//2018/10/25 频次数组
public final Map<String, Double> freqs = new HashMap<String, Double>();
public final Set<String> loadedPath = new HashSet<String>();
private Double minFreq = Double.MAX_VALUE;
private Double total = 0.0;
private DictSegment _dict;
private WordDictionary() {
this.loadDict();
this.loadUserDict();
this.loadUserStopDict();
}
public static WordDictionary getInstance() {
if (singleton == null) {
synchronized (WordDictionary.class) {
if (singleton == null) {
singleton = new WordDictionary();
return singleton;
}
}
}
return singleton;
}
/**
* for ES to initialize the user dictionary.
*
* @param configFile
*/
public void init(Path configFile) {
String abspath = configFile.toAbsolutePath().toString();
System.out.println("initialize user dictionary:" + abspath);
synchronized (WordDictionary.class) {
if (loadedPath.contains(abspath))
return;
DirectoryStream<Path> stream;
try {
stream = Files.newDirectoryStream(configFile, String.format(Locale.getDefault(), "*%s", USER_DICT_SUFFIX));
for (Path path : stream) {
System.err.println(String.format(Locale.getDefault(), "loading dict %s", path.toString()));
singleton.loadUserDict(path);
}
loadedPath.add(abspath);
} catch (IOException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
System.err.println(String.format(Locale.getDefault(), "%s: load user dict failure!", configFile.toString()));
}
}
}
/**
* let user just use their own dict instead of the default dict
*/
public void resetDict() {
_dict = new DictSegment((char) 0);
freqs.clear();
}
public void loadUserStopDict() {
InputStream is = this.getClass().getResourceAsStream(USER_STOP_DICT);
try {
BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
long s = System.currentTimeMillis();
while (br.ready()) {
String line = br.readLine();
String[] tokens = line.split("[\t ]+");
// 2018/10/25 每行参数小于两个为不正常。
// 词 \t 频率 \t 词性
if (tokens.length < 2)
continue;
String word = tokens[0];
double freq = Double.valueOf(tokens[1]);
total += freq;
stopWordFreqs.put(word, freq);
}
System.out.println(String.format(Locale.getDefault(), "user stop dict load finished, time elapsed %d ms",
System.currentTimeMillis() - s));
} catch (IOException e) {
System.err.println(String.format(Locale.getDefault(), "%s load failure!", USER_STOP_DICT));
} finally {
try {
if (null != is)
is.close();
} catch (IOException e) {
System.err.println(String.format(Locale.getDefault(), "%s close failure!", USER_STOP_DICT));
}
}
}
public void loadUserDict() {
InputStream is = this.getClass().getResourceAsStream(USER_DICT);
try {
BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
long s = System.currentTimeMillis();
while (br.ready()) {
String line = br.readLine();
String[] tokens = line.split("[\t ]+");
// TODO: 2018/10/25 每行参数小于两个为不正常。
// TODO: 词 \t 频率 \t 词性
if (tokens.length < 2)
continue;
String word = tokens[0];
double freq = Double.valueOf(tokens[1]);
total += freq;
// TODO: 2018/10/25 添加词语到前缀树里面,统计词频
word = addWord(word);
freqs.put(word, freq);
}
// normalize
for (Entry<String, Double> entry : freqs.entrySet()) {
entry.setValue((Math.log(entry.getValue() / total)));
minFreq = Math.min(entry.getValue(), minFreq);
}
System.out.println(String.format(Locale.getDefault(), "main dict load finished, time elapsed %d ms",
System.currentTimeMillis() - s));
} catch (IOException e) {
System.err.println(String.format(Locale.getDefault(), "%s load failure!", USER_DICT));
} finally {
try {
if (null != is)
is.close();
} catch (IOException e) {
System.err.println(String.format(Locale.getDefault(), "%s close failure!", USER_DICT));
}
}
}
public void loadDict() {
_dict = new DictSegment((char) 0);
InputStream is = this.getClass().getResourceAsStream(MAIN_DICT);
try {
BufferedReader br = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
long s = System.currentTimeMillis();
while (br.ready()) {
String line = br.readLine();
String[] tokens = line.split("[\t ]+");
// TODO: 2018/10/25 每行参数小于两个为不正常。
// TODO: 词 \t 频率 \t 词性(可省略)
if (tokens.length < 2)
continue;
String word = tokens[0];
double freq = Double.valueOf(tokens[1]);
total += freq;
// TODO: 2018/10/25 添加词语到前缀树里面,统计词频
word = addWord(word);
freqs.put(word, freq);
}
// normalize
for (Entry<String, Double> entry : freqs.entrySet()) {
entry.setValue((Math.log(entry.getValue() / total)));
minFreq = Math.min(entry.getValue(), minFreq);
}
System.out.println(String.format(Locale.getDefault(), "main dict load finished, time elapsed %d ms",
System.currentTimeMillis() - s));
} catch (IOException e) {
System.err.println(String.format(Locale.getDefault(), "%s load failure!", MAIN_DICT));
} finally {
try {
if (null != is)
is.close();
} catch (IOException e) {
System.err.println(String.format(Locale.getDefault(), "%s close failure!", MAIN_DICT));
}
}
}
private String addWord(String word) {
if (null != word && !"".equals(word.trim())) {
String key = word.trim().toLowerCase(Locale.getDefault());
// TODO: 2018/10/25 填充词典
_dict.fillSegment(key.toCharArray());
return key;
} else
return null;
}
public void loadUserDict(Path userDict) {
loadUserDict(userDict, StandardCharsets.UTF_8);
}
public void loadUserDict(Path userDict, Charset charset) {
try {
BufferedReader br = Files.newBufferedReader(userDict, charset);
long s = System.currentTimeMillis();
int count = 0;
while (br.ready()) {
String line = br.readLine();
String[] tokens = line.split("[\t ]+");
if (tokens.length < 1) {
// Ignore empty line
continue;
}
String word = tokens[0];
double freq = 3.0d;
if (tokens.length == 2)
freq = Double.valueOf(tokens[1]);
word = addWord(word);
freqs.put(word, Math.log(freq / total));
count++;
}
System.out.println(String.format(Locale.getDefault(), "user dict %s load finished, tot words:%d, time elapsed:%dms", userDict.toString(), count, System.currentTimeMillis() - s));
br.close();
} catch (IOException e) {
System.err.println(String.format(Locale.getDefault(), "%s: load user dict failure!", userDict.toString()));
}
}
public DictSegment getTrie() {
return this._dict;
}
public boolean containsWord(String word) {
return freqs.containsKey(word);
}
public boolean containsStopWord(String word) {
return stopWordFreqs.containsKey(word);
}
public Double getFreq(String key) {
if (containsWord(key))
return freqs.get(key);
else
return minFreq;
}
}
5. 多线程下的问题
因为getInstance()方法不是同步方法,所以,会出现两次获取单例类实例,返回两个不同的对象。
解决方案1:
加上synchronized关键词。变成同步方法,这样就可以保证单例类的实例唯一化了。但是这样会造成额外负担,如果能接受,就不用多想了。如果getInstance()使用在频繁运行的地方,就考虑下下面的方案吧。
public class Singleton {
//利用静态变量来记录Singleton类的唯一实例
private static Singleton uniqueInstance;
//把构造器声明为私有,确保只有自身Singleton类内才可以调用构造器。
private Singleton() {
}
//用getInstance()方法实例化对象,并返回这个实例
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
//当需要Singleton实例时,uniqueInstance为空,我们则利用私有的构造器,产生一个Singleton实例,并赋值到uniqueInstance静态变量中。
//当我们不需要时,它就永远不会产生,这就是"延迟实例化"
}
return uniqueInstance;
}
}
解决方案2:
"直接"创建实例,而非延迟实例化的做法.如果应用程序总是创建并使用单例实例,或者在创建,运行时方面的负担不大,就可以使用下面的代码,"直接"创建此单例。
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
//把构造器声明为私有,确保只有自身Singleton类内才可以调用构造器。
private Singleton() {
}
//用getInstance()方法实例化对象,并返回这个实例
public static Singleton getInstance() {
return uniqueInstance;
}
}
解决方案3:
双重检查加锁,在getInstance()中减少使用同步。
利用双重检查加锁,首先检查实例是否已经创建,如果为创建,才进行同步。这样只有第一次会同步。
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
6. 引用
Head First 设计模式