单例模式(Singleton Pattern)
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
-
实现单例模式的思路是:
- 一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);
- 当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;
- 同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
-
注意点:
- 单例模式在多线程的应用场合下必须小心使用。
-
构建方式:
- 懒汉方式:在全局的单例实列在第一次被使用时创建(在第一次调用的时就实列化)。
- 饿汉方式:在全局的单例实例在类装载时构建(在类初始化时,已经自行实列化)。
-
第一种实现(饿汉式)
package 单例模式; /** * 第一种实现单例模式 * 饿汉式 * 类加载到内存后,就实列化一个单例,JVM保证线程安全 * 缺点:不管你是否是用,类转载时就完成实列化; */ public class Mgr01 { private static final Mgr01 INSTANCE = new Mgr01(); private Mgr01() {} public static Mgr01 getINSTANCE() {return INSTANCE;} public static void main(String[] args) { Mgr01 instance1 = Mgr01.getINSTANCE(); Mgr01 instance2 = Mgr01.getINSTANCE(); System.out.println(instance1==instance2); } }
这种最简单的,也能保证线程的安全。
-
第二种实现(懒汉式)
package 单例模式; /** *第二种实现单例 */ public class Mgr02 { private static final Mgr02 INSTANCE; static { INSTANCE = new Mgr02(); } public static Mgr02 getINSTANCE() {return INSTANCE;} public static void main(String[] args) { Mgr02 instance1 = Mgr02.getINSTANCE(); Mgr02 instance2 = Mgr02.getINSTANCE(); System.out.println(instance1==instance2); } }
这种方式与第一种大致是没什么区别的
-
第三种实现(懒汉式)
package 单例模式; public class Mgr03 { private static Mgr03 INSTANCE; private Mgr03() {} public static Mgr03 getINSTANCE(){ if (INSTANCE == null){ INSTANCE = new Mgr03(); } return INSTANCE; } }
这种方式解决了上面两种的缺点,达到了按需求初始化,但是这种方式线程不安全。
- 测试线程
package 单例模式; public class Mgr03 { private static Mgr03 INSTANCE; private Mgr03() {} public static Mgr03 getINSTANCE(){ if (INSTANCE == null){ try { Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } INSTANCE = new Mgr03(); } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> System.out.println(Mgr03.getINSTANCE().hashCode())).start(); } } }
结果:
-
第四种实现单例(懒汉式)
既然第三种会出现线程问题,那么我们可以给第三种实现方式加上锁 ,这种解决的线程问题但是效率会下降
package 单例模式; public class Mgr04 { private static Mgr04 INSTANCE; private Mgr04() {} public static synchronized Mgr04 getINSTANCE(){ if (INSTANCE == null){ try { Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } INSTANCE = new Mgr04(); } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> System.out.println(Mgr04.getINSTANCE().hashCode())).start(); } } }
-
第五种方式(懒汉式)
既然第斯种会出现效率会下降,我们可以给synchronized加再需要的时候
package 单例模式; public class Mgr05 { private static Mgr05 INSTANCE; private Mgr05() {} public static Mgr05 getINSTANCE(){ if (INSTANCE == null){ synchronized(Mgr05.class){ try { Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } INSTANCE = new Mgr05(); } } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> System.out.println(Mgr05.getINSTANCE().hashCode())).start(); } } }
但是这种方式又会出现线程不安全问题,会创建多个实例,不可行。
-
第六种方式(懒汉式 双重检验锁)
既然第五种会出现线程问题,我们可以给他再进行一次判断
package 单例模式; public class Mgr06 { private static Mgr06 INSTANCE; private Mgr06() {} public static Mgr06 getINSTANCE(){ if (INSTANCE == null){ synchronized(Mgr06.class){ if (INSTANCE == null) { try { Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } INSTANCE = new Mgr06(); } } } return INSTANCE; } public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(() -> System.out.println(Mgr06.getINSTANCE().hashCode())).start(); } } }
-
第七种方式
package 单例模式; /** * 静态内部类 * JVM保证单例 * 加载外部类时不会加载内部类,这样可以实现懒加载 */ public class Mgr07 { private Mgr07(){} private static class Holder{ private final static Mgr07 INSTANCE = new Mgr07(); } public static Mgr07 getInstance(){ return Holder.INSTANCE; } }
-
上面方式都可以通过反射机制调用私有的构造器,得到多个实例。
package 单例模式; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Main { public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Constructor<Mgr02> declaredConstructor = Mgr02.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); Mgr02 newInstance1 =declaredConstructor.newInstance(); Mgr02 newInstance2 =declaredConstructor.newInstance(); System.out.println(newInstance1.equals(newInstance2)); } }
这里返回是false,说明创建了多个实例
-
第八种方式
这是最终的方式,也是最完美的方式 不仅解决了线程同步,含可以防止反序列化
package 单例模式; public enum Mgr08 { INSTANCE; private Mgr08() {} }