单例模式(Singleton pattern)是Java中最简单的设计模式之一。这种设计模式属于创建型模型,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1.饿汉式
定义:类加载就会导致该单实例对象被创建
1.1静态变量方式
/**
* @author 晓风残月Lx
* @date 2023/6/16 21:34
* 饿汉式 方式一 静态成员变量
*/
public class Singleton {
// 1.私有构造方法
private Singleton() {
}
// 2.在本类中创建苯类对象
private static Singleton instance = new Singleton();
// 3.提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance() {
return instance;
}
}
说明:该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象,该对象是随着类的加载而创建的。如果该对象占用内存较大,而一直没有使用就会造成内存的浪费。
1.2静态代码块
/**
* @author 晓风残月Lx
* @date 2023/6/16 21:39
* 饿汉式 方式二 静态代码块
*/
public class Singleton {
// 私有构造方法
private Singleton() {
}
// 声明Singleton类型的变量
private static Singleton instance;
// 在静态代码块中进行复制
static {
instance = new Singleton();
}
// 对外提供一个获取该类的方法
public static Singleton getInstance() {
return instance;
}
}
说明:该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块,是随着类的加载而创建的,和静态变量方式一样,也可能会产生内存浪费问题。
2.懒汉式
定义:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
2.1 线程不安全
/**
* @author 晓风残月Lx
* @date 2023/6/16 21:41
* 懒汉式 线程不安全
*/
public class Singleton {
// 私有构造方法
private Singleton() {
}
// 声明singleton类型的变量instance
private static Singleton instance;
// 对外提供获取类的方法
public static Singleton getInstance() {
// 懒汉式就是第一次访问才会创建对象,后面再访问直接返回了
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
说明:在成员位置声明Singleton类型的静态变量,但并没有进行对象的赋值操作,而是在调用getInstance()方法获取Singleton类对象的时候才创建Singleton类的对象,实现了懒加载的效果。
问题:在多线程环境中,可能会生成多个对象,破坏了单例。
2.2 线程安全
2.2.1方法上加synchronized
/**
* @author 晓风残月Lx
* @date 2023/6/16 21:50
* 懒汉式 线程安全 性能不高
*/
public class Singleton {
// 私有构造方法
private Singleton() {
}
// 声明singleton类型的变量instance
private static Singleton instance;
// 对外提供获取类的方法
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
说明:该方式实现了懒加载的效果,同时也解决了线程安全问题,但是在getInstance()方法上加入了synchronized关键字,导致该方法执行效率低,线程不安全只会出现在首次创建对象,后面就直接返回并不存在线程安全问题了。
2.2.2 双重检测锁
/**
* @author 晓风残月Lx
* @date 2023/6/16 22:14
* 懒汉式 线程安全 双重检测锁 不加volatile 可能会发生指令重排序
* volatile 保证有序性和可见性
*/
public class Singleton {
// 私有构造方法
private Singleton() {
}
// 声明singleton类型的变量instance
private static volatile Singleton instance;
// 对外提供获取类的方法
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
说明:该方法实现了懒加载效果,也只有初次加载创建的时候才会有锁,如果不加volatile关键字的话,看上去没有问题,其实在多线程环境下,可能会出现空指针异常,原因是在JVM在实例化对象的时候会进行优化和指令重排序,也就在 instance = new Singleton,因为这步并不是个原子操作,JVM会分为三步执行。1.分配内存空间。2.执行构造放,初始化对象。3.把对象指向到内存空间。他可能会发生123,132顺序出现,导致返回的instance还没有执行构造方法就返回了,导致下个线程直接返回instance,但此时线程一并没有完成初始化对象。加上volatile关键字以后,可以保证有序性和可见性。
2.2.3 静态内部类
/**
* @author 晓风残月Lx
* @date 2023/6/16 22:20
* 懒汉式 静态内部类方式 推荐
* JVM加载外部类的过程中,是不会加载静态内部类的,只有属性或方法被调用的时候才会首次加载
*/
public class Singleton {
private Singleton() {
}
private static class SingletonHandler {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供静态方法获取该对象
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
}
说明: JVM加载外部类的过程中,是不会加载静态内部类的,只有属性或方法被调用的时候才会首次加载。第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder,并初始化INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
2.2.4枚举
枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
/**
* @author 晓风残月Lx
* @date 2023/6/16 22:27
* 饿汉式 枚举 唯一一种不会被破坏的单例
*/
public enum Singleton {
INSTANCE;
}
3.破坏单例以及防止被破坏
3.1 序列化和反序列化
随便拿一个做例子
3.1.1破坏单例
import java.io.Serializable;
/**
* @author 晓风残月Lx
* @date 2023/6/16 22:14
*/
public class Singleton implements Serializable {
// 私有构造方法
private Singleton() {
}
private static class SingletonHandler {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供获取类的方法
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
}
import java.io.*;
/**
* @author 晓风残月Lx
* @date 2023/6/16 22:52
* 序列化 反序列化破坏单例
*/
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:\\software\\Java\\IDEA\\workspace\\design_gof23\\a.txt"));
// 2.读对象
Singleton instance = (Singleton) ois.readObject();
System.out.println(instance);
// 3.释放资源
ois.close();
}
// 向文件中写数据(对象)
public static void writeObject2File() throws Exception {
// 1.获取Singleton对象
Singleton instance = Singleton.getInstance();
// 2.创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\software\\Java\\IDEA\\workspace\\design_gof23\\a.txt"));
// 3.写对象
oos.writeObject(instance);
// 4.释放资源
oos.close();
}
}
运行以后发现两个并不相同,序列化和反序列化每次读出来的都是一个新对象。
3.1.1避免被破坏
/**
* @author 晓风残月Lx
* @date 2023/6/16 23:04
* 解决序列化、反序列化破坏单例
*/
public class Singleton implements Serializable {
// 私有构造方法
private Singleton() {
}
private static class SingletonHandler {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供获取类的方法
public static Singleton getInstance() {
return Singleton.SingletonHandler.INSTANCE;
}
// 当进行反序列化时会自动调用该方法,将该方法的返回值直接返回
public Object readResolve() {
return SingletonHandler.INSTANCE;
}
}
加一个readResolve方法,但为什么这样有效呢?
在Client测试方法中的对象输入流对象的读操作使用了readObject()方法,其底层会判断类是否有readResolve()方法,有的话直接执行,没有就重新创建。
3.2 反射
3.2.1破坏单例
/**
* @author 晓风残月Lx
* @date 2023/6/16 22:14
*/
public class Singleton {
// 私有构造方法
private Singleton() {
}
private static class SingletonHandler {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供获取类的方法
public static Singleton getInstance() {
return SingletonHandler.INSTANCE;
}
}
import java.lang.reflect.Constructor;
/**
* @author 晓风残月Lx
* @date 2023/6/16 22:52
* 测试使用反射破坏单例模式
*/
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 singleton1 = cons.newInstance();
Singleton singleton2 = cons.newInstance();
System.out.println(singleton1 == singleton2);
}
}
会发现所获得的类对象内存地址不同文章来源:https://www.toymoban.com/news/detail-498553.html
3.2.2 解决
/**
* @author 晓风残月Lx
* @date 2023/6/16 23:16
* 防止反射破坏单例
*/
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 SingletonHandler {
private static final Singleton INSTANCE = new Singleton();
}
// 对外提供获取类的方法
public static Singleton getInstance() {
return Singleton.SingletonHandler.INSTANCE;
}
}
当通过反射方式调用构造方法进行创建创建时,直接抛异常。不运行此中操作。文章来源地址https://www.toymoban.com/news/detail-498553.html
到了这里,关于Gof23设计模式之单例模式(完整)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!