在什么情况下使用单例模式,请写出三种单例模式的代码实现。简单比较下此三种方法的优缺点。
大部分为原创,部分转载所以这里注明为转载。
使用单例模式的情况:
和名字中说的一样,单例模式即我们在只使用一个例子时候所要采取的一种模式。
下面具体说说单例模式的应用场景。
1.最简单的就是windows的taskmanager。我们尝试后可以发现我们在同一时刻只能打开一个taskmanager。这既是用单例模式实现的。
2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
下面我们简单解释一下最简单的三种单例模式
1.饿汉式单例模式饿汉式单例类饿汉式单例类是在Java 语言里实现得最为简便的单例类。在类被加载时,就会将自己实例化。
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
/**
* 私有默认构造子
*/
private EagerSingleton(){}
/**
* 静态工厂方法
*/
public static EagerSingleton getInstance(){
return instance;
}
}
优点:1.本模式本身比较容易,在我们这个类被加载的时候,我们的实例就创建了出来,不需要再进行之后的判断。
2.这个方法是线程安全的。
3.因为在类加载的同时对象已经创建,所以对于我们来说这样调用的速度是更快的。
缺点:1.本方法的资源利用率不高,无论在调用该类的其他方法或者加载了该类的时候都会创建一个这个类的实例,而且该实例都会被初始化。
2.懒汉式单例模式。
懒汉式非常形象只有你要getinstance时候,我们才讲对象实例化。
public class Singleton {
public static Singleton theInstance = null;
private Singleton(){}
public synchronized static Singleton instance(){
if(theInstance == null){
return new Singleton();
}
return theInstance;
}
}
优点:1.相对于饿汉式单例模式来说,不执行getinstance()时就不会实例化,可以执行该类的其他静态方法。
缺点。1.第一次加载不够快(因为当你加载的时候需要判断后再进行创建)
2.多线程时可能出现问题,因为当我们同时有多个线程访问getinstance()这个函数的时候,我们可能同时判断发现instance 都是为空的,这时可能会有多个线程同时创建instance的实例,这样就违反了单例模式的初衷。
解决方法。使用我们上个题目中学习的多线程的sunchronized关键字的加锁机制。getinstance本身只能被一个线程进行访问。
代码如下。
class Singleton3 {
private Singleton3() {
}
public static Singleton3 instance = null;
public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
改进后的方法仍然存在一个问题:
就是时间以及判断的开销比较大。我们可以采用双重检测。因为如上所说,只有当没有instance时,即instance为空时,我们才有可能多线程出现问题。所以我们使用双重检测可以减少这种开销。
class Singleton4 {
private Singleton4() {
}
public static Singleton4 instance = null;
public static Singleton4 getInstance() {
if (instance == null) {
synchronized (Singleton4.class) {
if (instance == null) {
instance = new Singleton4();
}
}
}
return instance;
}
}
3.登记式单例模式。
该模式是gof为了克服以上两个单例均不可继承的缺点而设计的。
public class RegSingleton {
static private HashMap m_registry = new HashMap();
static {
RegSingleton x = new RegSingleton();
m_registry.put(x.getClass().getName(), x);
}
protected RegSingleton() {
}
public static RegSingleton getInstance(String name) {
if (name == null) {
name = "com.javapatterns.singleton.demos.RegSingleton";
}
if (m_registry.get(name) == null) {
try {
m_registry.put(name, Class.forName(name).newInstance());
} catch (ClassNotFoundException cnf) {
System.out.println("Couldn't find class " + name);
} catch (InstantiationException ie) {
System.out.println("Couldn't instantiate an object of type "+ name);
} catch (IllegalAccessException ia) {
System.out.println("Couldn't access class " + name);
}
}
return (RegSingleton) (m_registry.get(name));
}
// sub-class implements RegSingleton.
public class RegSingletonChild extends RegSingleton {
public RegSingletonChild() {
}
static public RegSingletonChild getInstance() {
return (RegSingletonChild) RegSingleton
.getInstance("com.javapatterns.singleton.demos.RegSingletonChild");
}
public String about() {
return "Hello, I am RegSingletonChild.";
}
}
优点:实现了继承。
缺点:在GoF 原始的例子中,并没有getInstance() 方法,这样得到子类必须调用的getInstance(String name)方法并传入子类的名字,因此很不方便。加入getInstance() 方法的好处是RegSingletonChild 可以通过这个方法,返还自已的实例。而这样做的缺点是,由于数据类型不同,无法在RegSingleton 提供这样一个方法。由于子类必须允许父类以构造子调用产生实例,因此,它的构造子必须是公开的。这样一来,就等于允许了以这样方式产生实例而不在父类的登记中。这是登记式单例类的一个缺点。GoF 曾指出,由于父类的实例必须存在才可能有子类的实例,这在有些情况下是一个浪费。这是登记式单例类的另一个缺点。