Singleton——单例模式(8种)

单例模式:单例类只创建一个对象,类提供一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。

demo:

饿汉式:

静态常量方式:

public class Singleton {
    //1.构造器私有化,外部不能new
    private Singleton(){}

    //2.本类内部创建对象实例
    private final static Singleton instance=new Singleton();

    //3.提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance(){
        return instance;
    }
}

静态代码块方式:

public class Singleton {
    //1.构造器私有化,外部不能new
    private Singleton() {
    }

    //2.本类内部创建对象实例
    private static Singleton instance;

    //静态代码块随着类的加载而执行,而且只执行一次
    static {
        //在静态代码块中创建的对象是单例的
        instance = new Singleton();
    }

    //3.提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

饿汉式单例模式在类装载的时候完成实例化,避免了线程同步问题,但没有达到懒加载的效果,如果从始至终都没用过这个实力就会造成内存浪费。

 

懒汉式:

线程不安全:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公有方法,用到该方法时才去创建instance
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

将饿汉方式中类装载时就实例化对象的代码放到了静态方法里,这样需要用类对象的时候调静态方法实现类的实例化,不用的时候就不实例化,达到懒加载的效果,但多线程情况下会导致instance=null处的判断失效,从而使单例失效,所以这种方式是线程不安全的

线程安全:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公有方法,用到该方法时才去创建instance,保证懒加载
    //加入同步处理代码,解决线程安全问题
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

针对线程不安全,加了synchronized关键字,同步处理代码,既保证了懒加载又保证了线程安全

线程安全的单例模式还有如下写法,但不推荐使用:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    /*
    * 懒汉式-线程安全-同步代码块
    * 不推荐使用
    * */
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

 

双重检查:

public class Singleton {

    //为什么用volatile:
    //new对象时:
    //1.分配内存空间
    //2.执行构造方法,初始化对象
    //3.把这个对象指向这个空间
    //当new时底层不按123的顺序执行时,第一个判断处显示!null但对象还没完成构造,会出错,所以用volatile:避免指令重排
    private static volatile Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                   instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这个看注释就ok了

 

静态内部类:

public class Singleton {
    //1.构造器私有化,外部不能new
    private Singleton() {
    }

    //2.写一个静态内部类,该类中有一个静态属性 Singleton
    private static class SingletonInstance {
        private static final Singleton INSTANCE=new Singleton();
    }

    //3.提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE,synchronized:保证线程安全
    public static synchronized Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

之前掉单例对象的时候都是 类.静态方法 ,静态内部类的写法相当于将调用的代码做了一层封装,在单例类型写个静态内部类,在单例类中写个公开的方法调用静态内部类的方法实例化出单例对象,通过线程同步机制保证外界调用时的线程安全;就不用之前的 instance == null 判断了,执行效率高了点

 

以上单例方式的客户端调用方式:

public class Singleton01 {
    public static void main(String[] args) {
        // 测试
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode="+instance1.hashCode());
        System.out.println("instance2.hashCode="+instance2.hashCode());
    }
}

观察上述7种实现方式可以发现保证单例的核心是构造器私有化,让外部不能new它,然后在类内部提供一种得到类对象的方法供外界调用,单例模式都完美的利用了java的语法特性。但当看到私有化(private)时大家有没有想到什么,——“万恶”的反射,通过反射可以坏private,从而破坏单例,当然这是极端情况,但有就不能疏忽。那说了那么多那不白撤了吗,莫慌,来看看枚举是怎么做的。

 

单例模式——枚举方式:

enum Singleton {

    //利用枚举特性将对象托给枚举
    INSTANCE;

}

客户端调用:

public class Singleton07 {
    public static void main(String[] args) {
        // 测试
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2);
        System.out.println("instance1.hashCode=" + instance1.hashCode());
        System.out.println("instance2.hashCode=" + instance2.hashCode());

    }
}

为什么枚举不能被反射破坏的呢,来看反射的代码是怎么约束反射的:

反射不能实例化枚举类

Singleton——单例模式(8种)_第1张图片

这下就清楚了吧。

 

总结:对频繁创建销毁的对象,使用单例可以提高系统性能;实例化一个单例类时,必须调用效应的获取对象的方法,而不是用new。

单例模式适用情景:创建对象耗时过多或耗资源过多;经常用的对象如工具类;频繁访问数据库或文件的对象如数据源、session工厂等

 

你可能感兴趣的:(java设计模式)