赞
踩
(1)单例模式 (Singleton Pattern) 是 Java 中最简单的设计模式之一。它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
(2)单例模式的目的是限制一个类的实例数量,并提供全局访问点以方便其他组件使用。它通常在需要共享资源、管理全局状态或限制某个组件数量的情况下使用。然而,单例模式也有一些缺点,例如增加了代码的耦合性和可扩展性的限制,因此在使用时需要权衡其利弊。
(3)单例模式的主要有单例类和访问类这两个角色:
单例类 | 只能创建一个实例的类 |
---|---|
访问类 | 使用单例类 |
单例模式分类两种:饿汉式和懒汉式。
饿汉式 | 类加载就会导致该单实例对象被创建 |
---|---|
懒汉式 | 类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建 |
package com.itheima.patterns.singleton.hungryman1; //饿汉式 public class Singleton{ //私有构造方法 private Singleton(){} //静态变量创建类的对象 private static Singleton instance = new Singleton(); //对外提供静态方法获取该对象 public static Singleton getInstance(){ return instance; } }
说明: 方式一在成员位置声明 Singleton 类型的静态变量,并创建 Singleton 类的对象 instance。instance 对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
package com.itheima.patterns.singleton.hungryman2; public class Singleton{ //私有构造方法 private Singleton(){} //在静态代码块中进行创建 private static Singleton instance; static { instance = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance(){ return instance; } }
说明:方式二在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。所以和方式一基本一样,也存在内存浪费问题。
enum Singleton { //INSTANCE; INSTANCE("Tom", 21); // 添加其他成员变量 private String name; private int age; Singleton(String name, int age) { this.name = name; this.age = age; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } // 添加其他方法 public void sayHello() { System.out.println("Hello, I'm " + name + ", " + age + " years old."); } }
说明:枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。测试代码如下:
Client.java
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println("instance1 == instance2 的结果为: " + (instance1 == instance2)); // true
System.out.println(instance1.getAge());
instance1.setAge(50);
System.out.println(instance2.getAge());
}
}
输出结果如下:
instance1 == instance2 的结果为: true
21
50
package com.itheima.patterns.singleton.Lazyman; public class Singleton{ //私有构造方法 private Singleton(){} //声明 Singleton 类型的变量 instance,并未进行赋值 private static Singleton instance; //使用关键字 synchronized 的目的在于保证线程安全 public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
说明:该方式实现了懒加载效果,同时又解决了线程安全问题。但是在 getInstance() 方法上添加了 synchronized 关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化 instance 的时候才会出现线程安全问题,一旦初始化完成就不存在了。
public class Singleton { //私有构造方法 private Singleton(){} //声明 Singleton 类型的变量 instance,并未进行赋值 private static volatile Singleton instance; public static Singleton getInstance(){ //第一次检查,若 instance 不为 null,则不进入抢锁阶段,直接返回实际值即可 if (instance == null) { synchronized(Singleton.class){ //第二次检查,得到锁之后再次判断 instance 是否为空 if (instance == null) { instance = new Singleton(); } } } return instance; } }
(1)双重检验锁 (Double-Checked Locking) 是一种在多线程环境下实现单例模式的优化方式。其主要原理如下:
(2)这种方式结合了懒加载和线程安全的特点,在第一次调用时才创建实例,在多线程环境下确保只有一个实例被创建。双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题。
(3)在双重检查锁的实现方式中,如果没有使用 volatile
关键字,可能会出现指令重排序的问题。具体来说,当一个线程进入第一个 if
判断时,如果实例还未被创建,那么它会获取锁并创建实例。但是由于指令重排序的影响,实例的初始化可能会在获取锁之前被重排序到锁的后面,这就导致其他线程在第二个 if 判断中认为实例已经创建,从而返回一个未完全初始化的实例。
(4)通过使用 volatile 关键字修饰 instance 变量,可以保证在多线程环境下对 instance 的读取和写入操作都是有序的,避免了指令重排序问题,并且保证了其他线程能够正确地看到已经完全初始化的实例。
package com.itheima.patterns.singleton.lazyman3; public class Singleton { //私有构造方法 private Singleton(){} //静态内部类 private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
(1)第一次加载 Singleton 类时不会去初始化 INSTANCE,只有第一次调用 getInstance() 时,虚拟机才加载 SingletonHolder,并初始化 INSTANCE,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。总之,静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
(2)静态内部类实现的单例可以做到线程安全且可延迟加载的原因如下:
破坏单例模式演示(序列化反序列化和反射)
package com.itheima.patterns.singleton.problem1;
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.patterns.singleton.problem1; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; 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("E:\\testData\\a.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("E:\\testData\\a.txt")); //3.写对象 oos.writeObject(instance); //4.释放资源 oos.close(); } }
public class Singleton { //私有构造方法 private Singleton() {} private static volatile Singleton instance; //对外提供静态方法获取该对象 public static Singleton getInstance() { if(instance != null) { return instance; } synchronized (Singleton.class) { if(instance != null) { return instance; } instance = new Singleton(); return instance; } } }
import java.lang.reflect.Constructor; public class Client { 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 } }
(1)序列化、反序列化方式破坏单例模式的解决方法
在 Singleton 类中添加 readResolve() 方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。
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; } //解决序列化反序列化破解单例模式 private Object readResolve() { return SingletonHolder.INSTANCE; } }
具体的深入分析可以参考单例、序列化和 readResolve() 方法。
(2)反射方式破解单例的解决方法
当通过反射方式调用构造方法创建对象时,直接抛异常,不运行此种操作。
import java.io.Serializable; public class Singleton implements Serializable { private static boolean flag = false; //私有构造方法 private Singleton() { synchronized (Singleton.class){ //若 flag 的值为 true,说明不是第一次访问,直接抛一个异常 if (flag){ throw new RuntimeException("不能创建多个对象!"); } flag = true; } } private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } //对外提供静态方法获取该对象 public static Singleton getInstance() { return SingletonHolder.INSTANCE; } //解决序列化反序列化破解单例模式 private Object readResolve() { return SingletonHolder.INSTANCE; } }
但是上面的操作也不一定安全,因为可以通过反射的方式来修改 flag
,最安全的方式应该是使用枚举方式来创建单例对象,其原因在于 JDK 底层在通过反射创建对象时,会检查对象类型是否为枚举类型,如果是,则会抛出 IllegalArgumentException
异常,从而创建对象失败,这样做的目的在于保证枚举对象的单例性。具体相关源码如下所示:
@CallerSensitive public final class Constructor<T> extends Executable { //... public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } //检查给定的 clazz 对象的修饰符中是否包含枚举类型的标志位,以确定其是否表示一个枚举类型 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; } }
(1)单例模式是一种只允许创建一个实例的设计模式。它的主要优点是:
(2)然而,单例模式也有一些缺点:
(1)单例模式可以应用于许多场景,其中一些常见的应用场景包括:
(2)这只是一些常见的应用场景,实际上,任何需要全局访问点和只允许存在一个实例的情况都可以考虑使用单例模式。但请注意,在使用单例模式时需要慎重考虑其优缺点,并确保它满足设计需求。
(1)Runtime 类就是使用的单例设计模式,其部分源代码如下:
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {} ... }
从上面源代码中可以看出 Runtime
类使用的是饿汉式(静态属性)方式来实现单例模式的。
(2)使用 Runtime 类
import java.io.IOException; import java.io.InputStream; public class RunTimeDemo { public static void main(String[] args) throws IOException { //获取RunTime类对象 Runtime runtime = Runtime.getRuntime(); System.out.println("JVM 空闲内存 =" + runtime.freeMemory() / (1024*1024) + "M"); System.out.println("JVM 总内存 =" + runtime.totalMemory() / (1024*1024) + "M"); System.out.println("JVM 可用最大内存 =" + runtime.maxMemory() / (1024*1024) + "M"); //调用 runtime 的方法 exec,参数为一个命令 Process process = runtime.exec("ipconfig"); //调用 process 对象的获取输入流的方法 InputStream is = process.getInputStream(); byte[] arr = new byte[1024 * 1024 * 100]; int length = is.read(arr); System.out.println(new String(arr,0,length, "GBK")); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。