一、定义
单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的方法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。
二、构造条件
1、单例模式的类只提供私有的构造函数,即使用修饰符private修饰构造函数
2、类定义中含有一个该类的静态私有对象 SingletonClass instance(private+类名+单例对象名称)
3、类中提供一个公有的方法getInstance()来创建或获取它本身的静态私有对象
4、确保单例类对象在反序列化时不会重新构建对象
三、使用场景
当实例存在多个会引起程序逻辑错误的时候。
当资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
当控制资源的情况下,方便资源之间的互相通信。如线程池等
四、单例模式的书写形式
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。
第一种形式:懒汉式,即要在第一次被引用时,才会将自己实例化。
懒汉式的普通写法:
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
由于普通的写法不是线程安全的,便有了下面三种改进写法来保证线程安全
1、在getInstance上加synchronized关键字
public class SingletonClass{
private static SingletonClass instance=null;
public static synchronized SingletonClass getInstance()
{
if(instance==null)
{
instance=new SingletonClass();
}
return instance;
}
private SingletonClass(){
}
}
2、双重锁定形式
public class Singleton{
private static Singleton instance=null;
private Singleton(){
//do something
}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null) {
instance=new Singleton();
}
}
}
return instance;
}
}
3、静态内部类
public class Singleton{
private Singleton(){ }
private static class SingletoHolder (){
private static final Singleton mInstance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.mInstance;
}
}
第二种形式:饿汉式,即类加载的时候,就会直接将自己实例化
//对第一行static的一些解释
// java允许我们在一个类里面定义静态类。比如内部类(nested class)。
//把nested class封闭起来的类叫外部类。
//在java中,我们不能用static修饰顶级类(top level class)。
//只有内部类可以为static。
public class Singleton{
//在自己内部定义自己的一个实例,只供内部调用
private static final Singleton instance = new Singleton();
private Singleton(){
//do something
}
//这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance(){
return instance;
}
}
除了上面几种常见单例模式之外,我在看《effective java》时看到了另外一种单例模式。这种单例模式是通过枚举实现的,
这种方法的优点是可以的抵御恶意客户端通过反射的攻击并且不用担心反序列化的时候多次创建实例。
实战演示:
public enum EnumInstance {
INSTANCE;
public void print(){
System.out.print("Hello");
}
}
public class Test {
public static void main(String args[]) {
try {
Class<?> a = Class.forName("com.river.test.EnumInstance");
a.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果如下
可见不能通过反射创建枚举实例。
具体使用方式:
public class Test {
public static void main(String[] args) throws Exception {
EnumInstance s1 = EnumInstance .INSTANCE;
s1.print();
EnumInstance s2 = EnumInstance .INSTANCE;
s2.print();
System.out.println(s1 == s2);
}
}
测试结果:
Hello
Hello
true
五、总结
在多线程的环境下普通的懒汉式写法就显得不那么线程安全了。
getInstatnce()加同步 的优点是单例只有在使用时才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销。这种模式一般不建议使用。
DCL(双重锁定形式)的优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景小保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK6版本使用,否则,这种方式一般能够满足需求。
如果DCL不能满足你的要求的话,可以使用静态内部类单例模式,当第一次加载Singleton类时并不会初始化mInstance,只有在第一次调用Singleton的getInstance方法时才会导致mInstance被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方式。
懒汉式和饿汉式的比较
饿汉式:每当类加载的时候,就已经实例好了对象,调用时速度很快,这样虽然节省了时间,但却占用了内存空间。
懒汉式:恰恰和饿汉式相反,懒汉式虽然不占用内存空间,它只有在需要对象时才会进行初始化,但是它每次调用时都要进行判断在再实例化对象,很耗时。
参考书籍:《Android源码设计模式解析与实战》