java设计模式-单例模式

单例模式

 概述

  单例模式是 Java 比较简单,也是最基础,最常用的设计模式之一。在运行期间,保证某个类只创建一个实例,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  单例模式主要有饿汉式单例懒汉式单例静态内部类实现单例枚举类实现单例等,不同的方式有不同的优缺点,下面介绍各个实现方式和优缺点。

饿汉式单例

  创建饿汉式单例简单粗暴,在类被虚拟机加载时就创建类对象;

  缺点:可能在还不需要此实例的时候就已经把实例创建出来了,没起到lazy loading的效果;

  优点:实现简单,而且安全可靠;

package pattern.single;
/**
 * 饿汉式单例模式 不存在线程安全问题,因为在类加载时就创建好了对象
 * @author ningbeibei
 */
public class HungrySingle {
    // 当类加载时创建对象
    private static HungrySingle hungery = new HungrySingle();
    // 私有化构造函数,屏蔽外部创建对象
    private HungrySingle() {
    }
    // 提供全局访问点,直接返回实例对象
    public static HungrySingle getHungrySingle() {
        return hungery;
    }
}

  测试类代码

package pattern.single;
/**
 * 饿汉式单例模式测试类
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) {
        //饿汉式单例模式,获取 HungrySingle 实例
        HungrySingle hungry = HungrySingle.getHungrySingle();
        System.out.println("HungrySingle对象:"+hungry);
    }
}

  运行结果

 

 懒汉式单例

   相比饿汉式,懒汉式实现了用则创建不用则不创建,真正实现了懒加载效果;

package pattern.single;
/**
  *  懒汉式单例模式
 * @author ningbeibei
 */
public class SinglePattern {
    //声明对象变量
    private static SinglePattern single = null;
    //私有化构造函数
    private SinglePattern(){
    }
    //对外提供获取对象得方法
    public static SinglePattern getSinglePattern() {
        if(single==null){
            return new SinglePattern();
        }
        return single;
    }
}

  注意:上面代码实现了懒汉式单例,getSinglePattern()方法先判断实例是否为空再决定是否去创建实例,看起来似乎很完美,但是存在线程安全问题。在并发获取实例的时候,可能会存在构建了多个实例的情况。所以,需要对此代码进行下改进,确保实例唯一。

   改进后代码

package pattern.single;
/**
 * 双重检查加锁
 * 线程安全懒汉模式
 * @author ningbeibei
 */
public class LockSinglePattern {
    //声明变量, 使用volatile关键字确保绝对线程安全
    private volatile static LockSinglePattern lockSingle =null;
    //私有化构造函数
    private LockSinglePattern() {
    }
    //提供全局唯一获取实例方法
    public static LockSinglePattern getLockSinglePattern() {
        //判断实例是否null
        if(lockSingle==null) {
            //对单例类进行加锁
            synchronized (LockSinglePattern.class) {
                //在判断是否为null
                if(lockSingle==null) {
                    //创建实例
                    lockSingle = new LockSinglePattern();
                }
            }
        }
        //返回实例
        return lockSingle;
    }
}

  注意:这里采用了双重校验的方式,对懒汉式单例模式做了线程安全处理。通过加锁,可以保证同时只有一个线程走到第二个判空代码中去,这样保证了只创建 一个实例。这里还用到了volatile关键字来修饰lockSingle,其最关键的作用是防止指令重排。

  测试类

package pattern.single;
/**
 * 懒汉式单例模式测试类
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) {
        //双重加锁,线程安全懒汉式单例模式
        LockSinglePattern lock= LockSinglePattern.getLockSinglePattern();
        System.out.println("线程安全懒汉式单例模式:"+lock);
    }
}

  运行结果

 

 

 

静态内部类

  通过静态内部类的方式实现单例模式是线程安全的,因为内部静态类只会被加载一次,故该实现方式是线程安全的

  代码如下:

package pattern.single;
/**
 * 静态内部类实现单例模式
 * @author ningbeibei
 */
public class InteriorSingle {
    /**
     * 静态内部类
     * @author ningbeibei
     */
    private static class Insingle {
        //静态初始化器,由jvm来报证线程安全
        private static InteriorSingle single = new InteriorSingle();
    }
    //私有化构造函数
    private InteriorSingle() {
    }
    //提供全局唯一访问点
    public static InteriorSingle getSingle() {
        return Insingle.single;
    }
}

   注意:通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在InteriorSingle类加载时就加载,而是在调用getSingle()方法时才进行加载,达到了懒加载的效果。似乎静态内部类看起来已经是最完美的方法了,其实不是,可能还存在反射攻击或者反序列化攻击。

   测试类

package pattern.single;
/**
 * 懒汉式单例模式测试类
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) throws Exception {
        //静态内部类实现单例模式。这个也式线程安全得
        InteriorSingle Interior = InteriorSingle.getSingle();
        System.out.println("静态内部类单例模式:"+Interior);
    }
}

  运行结果

 

 

 枚举实现单例模式

   最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。而且写法还特别简单。

   代码如下

package pattern.single;
/**
 * 枚举类
 * @author ningbeibei
 */
public enum TypeSingle {
    // 定义一个枚举的元素,它 就代表了Singleton的一个实例
    TYPESINGLE;
    //业务方法
    public void get() {
        System.out.println("枚举中的方法");
    }
}

  测试代码

package pattern.single;
/**
 * 懒汉式单例模式测试类
 * @author ningbeibei
 */
public class test {
    public static void main(String[] args) throws Exception {
        //枚举实现单例模式
        TypeSingle.TYPESINGLE.get();
    }
}

  运行结果

 

 

 

破坏单例的两种方式

  反射破坏单例;

  序列化破坏单例;

  这两中破坏单例模式的方式不会对枚举单例方式构成威胁,所以一般都推荐枚举实现单例模式;

总结

  以上列举了多种单例模式的写法,在不同的场景中有不同应用;

  写的不足之处还望指正以便我及时更正避免读者误解;