单例模式:
理解:就是整个类中只有一个实例,并提供它的一个全局静态访问点。
方式:一个办法就是将构造函数变为私有的(至少是受保护的),使得外面的类不能通过引用来产生对象。
分类:单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象,多个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是不变单例对象,仅用做提供工具函数。
实现:
1) 饿汉式:
package 单实例模式;
public class A {
private static A a = new A();
/**
* 构造私有的构造方法
*/
private A(){
}
/**
* 得到类A的实例
*/
public static A getInstance(){
return a;
}
}
2)懒模式:
package 单实例模式;
public class B {
private static B instance = null;
/**
* 构造私有的构造方法
*/
private B(){
}
/**
* 得到类A的实例
*/
public static synchronized B getInstance(){
if( instance == null ){
instance = new B();
}
return instance;
}
}
3)注册表方式:
package 单实例模式;
import java.util.HashMap;
/**
* 注册表方式
* @author Administrator
*
*/
public class C {
//构建一个注册表
private static HashMap<String, Object> instance_HashMap = new HashMap<String, Object>();
protected C(){
}
/**
* 得到一个实例
* @param name 类名
* @return
*/
public static Object getInstance(String name){
if(name == null){
name = "Singleton";
}
if(instance_HashMap.get(name)==null){
try {
instance_HashMap.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return instance_HashMap.get(name);
}
package 单实例模式;
public class D extends C{
/**
* 因为子类的构造方法不能比父类的构造方法的范围小
*/
public D(){
}
public static void main(String[] args) {
//得到子类的一个实例,要添加包名
D d = (D)C.getInstance("单实例模式.D");
}
}
}
分析:首先前两种方式的构造函数都是私有的,彻底断开了使用构造函数来得到类的实例的通道,但是这样也使得类失去了多态性(大概这就是为什么有人将这种模式称作单态模式)。
在第二种方式中,对静态工厂方法进行了同步处理,原因很明显——为了防止多线程环境中产生多个实例;而在第一种方式中则不存在这种情况。
在第二种方式中将类对自己的实例化延迟到第一次被引用的时候。而在第一种方式中则是在类被加载的时候实例化,这样多次加载会照成多次实例化。但是第二种方式由于使用了同步处理,在反应速度上要比第一种慢一些。
在第三种方式中由于在 java 中子类的构造函数的范围不能比父类的小,所以可能存在不守规则的客户程序使用其构造函数来产生实例,造成单例模式失效。
弊端:
1)多个虚拟机:在不同虚拟机上,各个单例对象保存的状态很可能是不一样的。
2)多个类加载器:当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字节都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间(namespace)来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。
3)错误的同步处理:在使用懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能会达不到得到单个对象的效果,还可能引发死锁等错误。
4)子类破坏了对象控制:在使用注册表的单实例模式中有所体现。
5)串行化(可序列化):为了使一个单例类变成可串行化的,仅仅在声明中添加“implements Serializable”是不够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入 readResolve 方法。