设计模式—单例模式

**单例模式(Singleton Pattern)**是 java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的结构: 主要有以下角色:
  • 单例类。只能创建一个实例的类
  • 访问类。使用单例类

**单例模式的实现:**分两类:

饿汉式:
       * 类加载就会导致该单实例对象被创建 	
	   * 不存在线程安全问题 		
       * 浪费资源
懒汉式:
		* 类加载不会导致该单实例对象被创建,而是首次使用该对象时会创建
		* 存在线程安全问题
		* 不浪费资源
饿汉式1:静态变量创建类的对象(推荐使用)
/*
    饿汉式:
        静态变量创建类的对象
*/
public class Singleton {

    //私有构造方法
    private Singleton() {
    }

    //创建本类私有静态对象
    private static Singleton instance = new Singleton();

    //对外提供静态方法获取该对象
    //静态方法,使外界只能访问不能创建,保证单例
    public static Singleton getInstance() {
        return instance;//返回 Singleton 对象
    }

    public void showinfo(){
        System.out.println("hello world!");
    }
}

//测试类
public class Client {
    public static void main(String[] args) {

        //创建Singleton类的对象,私有的无法直接创建,
        // 只能用通过类名调用里面的方法进行获取该类的对象

        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        //结果为 true ,表示只创建了一个 Singleton 类的对象
        System.out.println("instance == instance1 = " + (instance == instance1));

        instance.showinfo();
        instance1.showinfo();
    }
}
饿汉式2: 静态代码块中创建该类对象
/*
    饿汉式:
        静态代码块中创建该类对象
*/
public class Singleton {
    //私有构造方法
    private Singleton(){}
    //声明 Singleton 类型的变量
    private static Singleton instance;

    //在静态代码块中进行赋值
    static{
        instance = new Singleton();
    }

    //对外提供获取该类对象的方法
    public static Singleton getInstance() {
        return instance;
    }

    // 公共的成员方法:showinfo()
    public void showinfo() {
        System.out.println("hello world!");
    }
}

public class Client {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);

        instance.showinfo();
        instance1.showinfo();
    }
}
两种饿汉式代码差异很小

饿汉式1:
//创建本类私有静态对象
private static Singleton instance = new Singleton();

饿汉式2:
//声明 Singleton 类型的变量
private static Singleton instance;
//在静态代码块中进行赋值
static{
instance = new Singleton();
}

懒汉式 1 (线程不安全)
懒汉式的主方法:
public class Client {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        System.out.println(instance == instance1);
    }
}
/**
* 懒汉式:
*    线程不安全
*/
public class Singleton {
    //私有构造方法
    private Singleton(){}

    //声明 Singleton 类型的变量 instance
    private static Singleton instance;

    //对外提供静态访问方法,获取该对象
    public static Singleton getInstance() {
        //判断 instance 是否为 null,如果为 null ,说明没有创建,需要创建一个
        if(instance ==null)
            instance=new Singleton();
        return instance;
    }
}
懒汉式 2(线程安全)

就是在 懒汉式 1 的 静态方法中加一个 synchronize 锁

/**
* 懒汉式:
*    线程不安全
*/
public class Singleton {
    //私有构造方法
    private Singleton(){}


    //声明 Singleton 类型的变量 instance
    private static Singleton instance;

    //对外提供静态访问方法,获取该对象
    public static synchronized Singleton getInstance() {
        //判断 instance 是否为 null,如果为 null ,说明没有创建,需要创建一个
        if(instance ==null)
            instance=new Singleton();
        return instance;
    }
}

该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在 getInstance() 方法上添加了 synchronized 关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化 instance 的时候才会出现线程安全问题,一旦初始化完成就不存在了。

懒汉式 3(线程安全:双重检查锁的方式 (DCL),即 double-checked locking)
/**
* 懒汉式
*      双重检查锁的方式
*/
public class Singleton {
    //私有构造方法
    private Singleton(){}
    //声明Singleton类型的变量
    private static Singleton instance;

    //对外提供公共的访问方式
    public static Singleton getInstance() {
        //第一次判断,如果 instance 不为null,不需要抢占锁,直接返回对象
        if (instance == null) {
            synchronized (Singleton.class) {
                //第二次判断,抢到锁后再次判断是否为 null
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检查锁模式解决了单例、性能、线程安全问题。但是,在多线程的情况下,可能会出现空指针问题,出现问题的原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

//声明Singleton类型的变量
    private static volatile Singleton instance;

添加 volatile 关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题。

懒汉式 4 (静态内部类方式)(实现 lazy loading 效果时,推荐使用)

静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有 内部类的属性/方法 被调用时才会被加载, 并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。

/**
* 懒汉式:(线程安全)
*      静态内部类方式
*/
public class Singleton {
    //私有构造方法
    private Singleton(){}

    //静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE =new Singleton();
    }

    //对外提供静态方法获取对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
说明:

第一次加载 Singleton 类时不会去初始化 INSTANCE ,只有第一次调用 getInstance ,虚拟机加载SingletonHolder 并初始化 INSTANCE ,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
小结:静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。

枚举方式:(饿汉式)( 涉及到反序列化创建对象时,推荐使用)

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

/**
* 枚举实现单例
*/
public enum Singleton {
    INSTANCE;
    public void showInfo() {
        System.out.println("hello world");
    }
}
public class Client {
    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 = Singleton.INSTANCE;
        System.out.println(instance == instance1);


        instance.showInfo();
        instance1.showInfo();
    }
序列化和反射会破坏单例模式:
   > 除了枚举方式外,其他的单例模式都会可以被破坏
1)序列化反序列化:

Singleton类:

import java.io.Serializable;

/**
* 懒汉式:静态内部类实现
*/
public class Singleton implements Serializable {


    //私有构造方法
    private Singleton() {
    }


    //静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Test类:

public class Test {
    public static void main(String[] args) throws Exception {

        //往文件中写对象
        writeObject2File();

        //调用方法,返回的是一个 Singleton 类型的 对象
        Singleton s1 = readObjectFromFile();
        Singleton s2 = readObjectFromFile();

        //判断两个反序列化后的对象是否是一个对象
        System.out.println(s1 == s2);
    }

    //
    private static Singleton readObjectFromFile() throws Exception {
        //创建 对象输入流 对象
        //FileInputStream(String name) 通过打开与实际文件的连接来创建 FileInputStream ,该文件由文件系统中的路径名 name命名。
        //ObjectInputStream(InputStream in) 创建一个从指定的 InputStream 读取的 ObjectInputStream

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("DesignMode\\resource\\a.txt"));

        //第一个读取 Singleton 对象
        //Object readObject() 从 ObjectInputStream 中读取一个对象。

        Singleton instance = (Singleton) ois.readObject();
        return instance;
    }

    //
    public static void writeObject2File() throws Exception {
        //获取 Singleton 类的对象
        Singleton instance = Singleton.getInstance();
        //创建 对象输出流 对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("DesignMode\\resource\\a.txt"));
        //将 instance 对象写到文件中
        oos.writeObject(instance);
    }
}
如何解决序列化破坏单例模式:

在Singleton类中添加 readResolve() 方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。

/**
* 懒汉式:静态内部类实现
*/
public class Singleton implements Serializable {

    //私有构造方法
    private Singleton() {
    }

    //静态内部类
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }

    //对外提供静态方法获取对象
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 下面是解决序列化和反序列化破坏单例模式的代码
     */
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}
2)反射

Singleton类:

/**
* 懒汉式:双重检查锁
*/
public class Singleton {

    private static volatile Singleton instance;

    //私有构造方法
    private Singleton() {
    }
    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        if (instance != null) {
            return instance;
        }
        synchronized (Singleton.class) {
            if (instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}

Test类:

public class Test {
public static void main(String[] args) throws Exception {
//获取 Singleton 类的 字节码对象
Class clazz = Singleton.class;
//获取 Singleton 类的 私有无参构造方法对象
Constructor constructor = clazz.getDeclaredConstructor();

    //取消访问检查
    constructor.setAccessible(true);

    //创建 Singleton 类的对象 s1
    Singleton s1 = (Singleton) constructor.newInstance();
    //创建 Singleton 类的对象 s2
    Singleton s2 = (Singleton) constructor.newInstance();

    //判断通过反射创建的两个 Singleton 对象是否是同一个对象
    System.out.println(s1 == s2);//false,表示反射破坏了单例设计模式
} }
如何解决反射破坏单例模式:
/**
* 懒汉式:双重检查锁
*/
public class Singleton {

    private static volatile Singleton instance;

    private static volatile boolean exist = false;//添加的代码

    //私有构造方法
    private Singleton() {
        /*
         * 反射破解单例模式需要添加的代码
         */
        if (!exist) {
            throw new RuntimeException("重复创建对象!!");
        }
        exist = true;
    }

    //对外提供静态方法获取该对象
    public static Singleton getInstance() {
        if (instance != null) {
            return instance;
        }
        synchronized (Singleton.class) {
            if (instance != null) {
                return instance;
            }
            instance = new Singleton();
            return instance;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值