Java教程

Java设计模式---单例设计模式

本文主要是介绍Java设计模式---单例设计模式,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

创建型模式

  • 创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。这样可降低系统的耦合度,使用者不需要关注对象的创建细节
  • 创建型模式分为:
    • 单例模式
    • 工厂方法模式
    • 抽象工程模式
    • 原型模式
    • 建造者模式

单例设计模式

  • 单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式
  • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
  • 单例设计模式分为两种:
    • 饿汉式:类加载就会导致该单实例对象被创建
    • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
1. 饿汉式
  • 方式1(静态变量方式)
package com.itheima.pattern.singleton.demo1;

/**
 * 饿汉式:静态成员变量
 */
public class Singleton {

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

    // 2. 在本类中创建本类对象
    private static Singleton instance = new Singleton();

    // 3. 提供一个公共的访问方式,让外界获取该对象
    public static Singleton getInstance(){
        return instance;
    }

}

测试代码

package com.itheima.pattern.singleton.demo1;

public class Client {
    public static void main(String[] args) {
        // 创建Singleton类的对象
        Singleton instance = Singleton.getInstance();

        Singleton instance1 = Singleton.getInstance();

        // 判断获取到的两个是否是同一个对象
        System.out.println(instance == instance1);
    }
}

运行结果

饿汉式方式一:静态变量方式

该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象instance。i
nstance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费
  • 饿汉式-方式2(静态代码块方式)
package com.itheima.pattern.singleton.demo2;

/**
 * 饿汉式:静态代码块
 */
public class Singleton {

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

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

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

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

测试代码

package com.itheima.pattern.singleton.demo2;

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

        System.out.println(instance == instance1);

    }
}

运行结果
饿汉式方式2-静态代码块方式

该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。
所以和饿汉式的方式1基本上一样,该方式也存在内存浪费问题
2. 饿汉式
  • 饿汉式-方式1(线程不安全)
package com.itheima.pattern.singleton.demo3;

/**
 * 饿汉式, 线程不安全
 */
public class Singleton {
    // 私有构造方法
    private Singleton(){}
    // 声明Singleton类型的变量instance
    private static Singleton instance; // 只是声明一个该类型的变量,并没有进行赋值
    // 对外提供访问方式
    public static Singleton getInstance(){
        // 判断instance是否为null, 如果为null,说明还没有创建Singleton类的对象
        // 如果没有,创建一个并返回,如果有,直接返回
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

测试代码

package com.itheima.pattern.singleton.demo3;

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

运行结果
懒汉式-方式1(线程不安全)

从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作,
当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载
的效果。但是,如果是多线程环境,会出现线程安全问题
  • 懒汉式-方式2(线程安全)
package com.itheima.pattern.singleton.demo4;

/**
 * 饿汉式, 线程安全
 */
public class Singleton {
    // 私有构造方法
    private Singleton(){}
    // 声明Singleton类型的变量instance
    private static Singleton instance; // 只是声明一个该类型的变量,并没有进行赋值
    // 对外提供访问方式
    public static synchronized Singleton getInstance(){
        // 判断instance是否为null, 如果为null,说明还没有创建Singleton类的对象
        // 如果没有,创建一个并返回,如果有,直接返回
        if (instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

测试代码

package com.itheima.pattern.singleton.demo4;

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

运行结果
饿汉式-方式2(线程安全)

该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,
导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,
一旦初始化完成就不存在了
  • 懒汉式-方式3(双重检查锁)
    再讨论一下懒汉模式中加锁的问题,对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁时机。由此也产生了一种新的实现模式:双重检查锁模式
package com.itheima.pattern.singleton.demo5;

/**
 * 双重检查锁方式
 */
public class Singleton {

    // 私有构造方法
    private Singleton(){}
    // 声明Singleton类型的变量
    private static Singleton instance;
    // 对外提供公共的访问方式
    public static Singleton getInstance(){
        // 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if (instance == null){
            synchronized (Singleton.class){
                // 第二次判断
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

测试代码

package com.itheima.pattern.singleton.demo5;

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

运行结果
懒汉式-方式3(双重检查锁)

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

下面为双重检查锁模式标准代码(声明 instance变量时加入volatile关键字):

package com.itheima.pattern.singleton.demo5;

/**
 * 双重检查锁方式
 */
public class Singleton {

    // 私有构造方法
    private Singleton(){}
    // 声明Singleton类型的变量
    private static volatile Singleton instance;
    // 对外提供公共的访问方式
    public static Singleton getInstance(){
        // 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象
        if (instance == null){
            synchronized (Singleton.class){
                // 第二次判断
                if (instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题
  • 懒汉式-方式4(静态内部类方式)
    静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序
package com.itheima.pattern.singleton.demo6;

/**
 * 饿汉式-静态内部类方式
 */
public class Singleton {
    // 私有化构造方法
    private Singleton(){}
    // 定义一个静态内部类
    private static class SingletonHolder{
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供公共的访问方式
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

测试代码

package com.itheima.pattern.singleton.demo6;

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

运行结果
懒汉式-方法4(静态内部类)

第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,
这样不仅能确保线程安全,也能保证Singleton类的唯一性。

静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费

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

/**
 * 枚举实现方式
 */
public enum Singleton {
    INSTANCE;
}

测试代码

package com.itheima.pattern.singleton.demo7;

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

运行结果
枚举方式

枚举方式属于饿汉式方式,不考虑浪费内存空间的话首选枚举方式

存在的问题

破坏单例模式:使上面定义的单例类(Singleton)可以创建多个对象,枚举方式除外。有两种方式,分别是序列化和反射
  • 序列化反序列化破环单例模式
package com.itheima.pattern.singleton.demo8;

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;
    }
}

package com.itheima.pattern.singleton.demo8;

import java.io.*;

public class Client {
    public static void main(String[] args) throws Exception {
        writeObject2File(); // 注意先写再读
        readObjectFromFile();
        readObjectFromFile();   // 看两次是否为同一对象
    }

    // 从文件读取数据(对象)
    public static void readObjectFromFile() throws Exception{
        // 1. 创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Java\\source_code\\DataStructures\\src\\com\\itheima\\pattern\\singleton\\demo8\\Test.txt"));
        // 2. 读取对象
        Singleton instance = (Singleton) ois.readObject();
        System.out.println(instance);
        // 释放资源
        ois.close();

    }

    // 向文件写数据(对象)
    public static void writeObject2File() throws Exception {
        // 1. 获取Singleton对象
        Singleton instance = Singleton.getInstance();
        // 2. 创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Java\\source_code\\DataStructures\\src\\com\\itheima\\pattern\\singleton\\demo8\\Test.txt"));
        // 3. 写对象
        oos.writeObject(instance);
        // 4. 释放资源
        oos.close();
    }
}

运行结果
在这里插入图片描述

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

package com.itheima.pattern.singleton.demo8;

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;
    }

    // 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve(){
        return SingletonHolder.INSTANCE;
    }
}

package com.itheima.pattern.singleton.demo8;

import java.io.*;

public class Client {
    public static void main(String[] args) throws Exception {
        writeObject2File();
        readObjectFromFile();
        readObjectFromFile();   // 看两次是否为同一对象
    }

    // 从文件读取数据(对象)
    public static void readObjectFromFile() throws Exception{
        // 1. 创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Java\\source_code\\DataStructures\\src\\com\\itheima\\pattern\\singleton\\demo8\\Test.txt"));
        // 2. 读取对象
        Singleton instance = (Singleton) ois.readObject();
        System.out.println(instance);
        // 释放资源
        ois.close();

    }

    // 向文件写数据(对象)
    public static void writeObject2File() throws Exception {
        // 1. 获取Singleton对象
        Singleton instance = Singleton.getInstance();
        // 2. 创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Java\\source_code\\DataStructures\\src\\com\\itheima\\pattern\\singleton\\demo8\\Test.txt"));
        // 3. 写对象
        oos.writeObject(instance);
        // 4. 释放资源
        oos.close();
    }
}

运行结果
在这里插入图片描述

  • 反射破坏单例模式
package com.itheima.pattern.singleton.demo9;

/**
 * 饿汉式-静态内部类方式
 */
public class Singleton {
    // 私有化构造方法
    private Singleton(){}
    // 定义一个静态内部类
    private static class SingletonHolder{
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供公共的访问方式
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}
package com.itheima.pattern.singleton.demo9;

import java.lang.reflect.Constructor;

/**
 * 测试使用反射破坏单例模式
 */
public class Client {
    public static void main(String[] args) throws Exception {
        // 1. 获取Singleton的字节码对象
        Class<Singleton> clazz = Singleton.class;
        // 2. 获取无参构造方法对象
        Constructor<Singleton> cons = clazz.getDeclaredConstructor();
        // 3. 取消访问检查
        cons.setAccessible(true);
        // 4. 创建Singleton对象
        Singleton s1 = cons.newInstance();
        Singleton s2 = cons.newInstance();
        System.out.println(s1 == s2); // 返回false,说明破坏了单例模式
    }
}

运行结果
反射破坏单例模式
【解决方法】

package com.itheima.pattern.singleton.demo9;

/**
 * 反射破坏内部类解决
 */
public class Singleton {

    private static boolean flag = false;

    // 私有化构造方法
    private Singleton() {
        synchronized (Singleton.class) {
            // 判断flag是否为true,如果是true,说明非第一次访问,直接抛出一个异常,如果是false,说明第一次访问
            if (flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            // 将flag的值设置为true
            flag = true;
        }

    }

    // 定义一个静态内部类
    private static class SingletonHolder {
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }

    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Runtime类使用了单例模式,如下图,使用了饿汉式模式
Runtime类中的饿汉式模式

这篇关于Java设计模式---单例设计模式的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!