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对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费
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); } }
运行结果
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。 所以和饿汉式的方式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); } }
运行结果
从上面代码我们可以看出该方式在成员位置声明Singleton类型的静态变量,并没有进行对象的赋值操作, 当调用getInstance()方法获取Singleton类的对象的时候才创建Singleton类的对象,这样就实现了懒加载 的效果。但是,如果是多线程环境,会出现线程安全问题
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); } }
运行结果
该方式也实现了懒加载效果,同时又解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字, 导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题, 一旦初始化完成就不存在了
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); } }
运行结果
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式在多线程的情况下, 可能会存在空指针的问题,出现问题原因是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关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下线程安全也不会有性能问题
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); } }
运行结果
第一次加载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); } }
运行结果
枚举方式属于饿汉式方式,不考虑浪费内存空间的话首选枚举方式
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类使用了单例模式,如下图,使用了饿汉式模式