单例模式
1. 介绍¶
单例模式(Singleton Pattern)是 Java
中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
特点
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
意图
:保证一个类仅有一个实例,并提供一个访问它的全局访问点。主要解决
:一个全局使用的类频繁地创建与销毁。何时使用
:当您想控制实例数目,节省系统资源的时候。如何解决
:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。关键代码
:构造函数是私有的。使用场景
: 要求生产唯一序列号,WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来,或者创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
2. 示例¶
饿汉式
package com.cmz.singleton; /** * 饿汉式 * 类加载到内存后,就实例化一个单例,JVM保证线程安全 * 简单实用,推荐使用! * 唯一缺点:不管用到与否,类装载时就完成实例化 * Class.forName("") * (话说你不用的,你装载它干啥) */ public class Mgr01 { //创建 Mgr01 的一个对象 private static final Mgr01 INSTANCE = new Mgr01(); //让构造函数为 private,这样该类就不会被实例化 private Mgr01() {}; //获取唯一可用的对象 public static Mgr01 getInstance() { return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { Mgr01 m1 = Mgr01.getInstance(); Mgr01 m2 = Mgr01.getInstance(); System.out.println(m1 == m2); } }
静态代码块
package com.cmz.singleton; /** * 跟01是一个意思,只不过是静态代码带而已 */ public class Mgr02 { private static final Mgr02 INSTANCE; static { INSTANCE = new Mgr02(); } private Mgr02() {}; public static Mgr02 getInstance() { return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { Mgr02 m1 = Mgr02.getInstance(); Mgr02 m2 = Mgr02.getInstance(); System.out.println(m1 == m2); } }
懒汉模式
package com.cmz.singleton; /** * lazy loading * 也称懒汉式,线程不安全 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题,这种方式是最基本的实现方式, * 这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。 * 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。 */ 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 void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()-> System.out.println(Mgr03.getInstance().hashCode()) ).start(); } } }
加锁的模式1: 懒汉模式
package com.cmz.singleton; /** * lazy loading * 也称懒汉式 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题 * 可以通过synchronized解决,但也带来效率下降 */ 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 void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr04.getInstance().hashCode()); }).start(); } } }
加锁的模式2: 懒汉模式
package com.cmz.singleton; /** * lazy loading * 也称懒汉式 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题 * 可以通过synchronized解决,但也带来效率下降 */ 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 void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr05.getInstance().hashCode()); }).start(); } } }
加锁的模式3: 懒汉模式
package com.cmz.singleton; /** * lazy loading * 也称懒汉式 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题 * 可以通过synchronized解决,但也带来效率下降 */ public class Mgr06 { private static volatile Mgr06 INSTANCE; //JIT 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 void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr06.getInstance().hashCode()); }).start(); } } }
静态内部类
package com.cmz.singleton; /** * 静态内部类方式 * JVM保证单例 * 加载外部类时不会加载内部类,这样可以实现懒加载 */ public class Mgr07 { private Mgr07() { } private static class Mgr07Holder { private final static Mgr07 INSTANCE = new Mgr07(); } public static Mgr07 getInstance() { return Mgr07Holder.INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr07.getInstance().hashCode()); }).start(); } } }
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch
提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
枚举法
package com.cmz.singleton; /** * 不仅可以解决线程同步,还可以防止反序列化。 */ public enum Mgr08 { INSTANCE; public void m() {} public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Mgr08.INSTANCE.hashCode()); }).start(); } } }
其实后面我们可以使用spring 来管理,使用bean工厂来保证单例。