单例模式简介
90%以上的设计模式都或多或少的应用了接口和抽象类,而单例比较特殊,并没有接口的应用。
数据库连接获取类的对象可以是单例的。
单例的意思就是在内存中只有一个对象。而单例和static有是有区别的,static是用来修饰类中的成员变量和成员方法的,而单例则属于对象的层面。
单例的实现
思考:如何在内存中只有一个对象?——不能让外界随意的实例化(new)。封装思想中有一个叫“属性私有化,方法公开化”的概念,在需要单例的类中自己去实例化。这就是单例的两个条件:不能被外界实例化;根据封装的特征属性私有化,方法公开化。
外界不能实例化,实现的方式就是将需要设置为单例的类(以下简称:单例类)的构造器设置为private。
注意:如果电脑上安装了多个虚拟机的话,那么实际上并不是真正意义上的单例,而是每个虚拟机会有一个单例。
懒汉单例模式:
package design.pattern; /** * 懒汉单例模式Demo<br>懒汉单例是在运行时调用的一种行为。 * 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。<br> * 这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。<br> * 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。<br> * 这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。<br> * * 类名:LazySingleton<br> * 作者: mht<br> * 微信:baozoutianwa<br> * 日期: 2018年3月18日-下午8:41:01<br> */ public class LazySingleton { /** * 属性私有化 */ private static LazySingleton instance = null; /** * 构造器私有化 */ private LazySingleton() { System.out.println("这是构造器:LazySingleton()"); } /** * 公开获取单例对象 <br> * 作者: mht<br> * 时间:2018年3月18日-下午8:48:42<br> */ public synchronized static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; } public void doSomething() { System.out.println("LazySingleton is doing something now ..."); } }
synchronized关键字是为了防止多线程穿行,也就是多个线程同时出现获取实例为null,从而创建多个实例的情况。
饿汉单例模式:
package design.pattern; /** * 饿汉单例模式Demo<br> * 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。<br> * 这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。<br> * 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。<br> * 这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。<br> * * 类名:EagerSingleton<br> * 作者: mht<br> * 微信:baozoutianwa<br> * 日期: 2018年3月18日-下午8:41:01<br> */ public class EagerSingleton { /** * 属性私有化 */ private static EagerSingleton instance = new EagerSingleton(); /** * 构造器私有化 */ private EagerSingleton() { System.out.println("这是构造器:LazySingleton()"); } /** * 公开获取单例对象 <br> * 作者: mht<br> * 时间:2018年3月18日-下午8:48:42<br> */ public static EagerSingleton getInstance() { return instance; } public void doSomething() { System.out.println("EagerSingleton is doing something now ..."); } }
饿汉单例模式与懒汉的不同在于创建对象的时机不同,饿汉单例会在类加载的时候即实例化对象,从而避免多线程下频繁判断对象是否为null的情况,性能上要优越一些。
测试:
package design.pattern; public class TestSingleton { public static void main(String[] args) { EagerSingleton es0 = EagerSingleton.getInstance(); EagerSingleton es1 = EagerSingleton.getInstance(); System.out.println(es0); System.out.println(es1); System.out.println(es0 == es1); } }
结果:
这是构造器:LazySingleton() design.pattern.EagerSingleton@15db9742 design.pattern.EagerSingleton@15db9742 true
懒汉加饿汉单例模式(私有内部类实现):
package design.pattern; /** * 懒汉饿汉结合的单例模式 * 是通过私有静态内部类来实现延迟加载的 * 优点是:不仅可以达到懒汉式的延迟加载,使类的加载速度提升,避免一开始占用过多内存,又具备线程安全性,无需判断,性能上 * 也会更快一些。 * <br>类名:StaticInnerClass<br> * 作者: mht<br> * 日期: 2018年3月19日-下午9:55:52<br> */ public class StaticInnerClass { // 私有静态内部类,按需加载,用时加载,也就是延迟加载 private static class Holder{ private static StaticInnerClass sic = new StaticInnerClass(); private Holder() { System.out.println("这是Holder构造器..."); } } private StaticInnerClass() { System.out.println("这是StaticInnerClass构造器..."); } public static StaticInnerClass getInstance() { return Holder.sic; } }
双重检查单例模式(线程安全的懒汉式单例):
package design.pattern; /** * 双重检查单例模式 使用双重检查同步延迟加载来创建单例的做法是一个非常优秀的做法, 其不但保证了单例(线程安全),而且切实提高了程序的运行效率。 <br> * 类名:DoubleCheckSingleton<br> * 作者: mht<br> * 日期: 2018年3月19日-下午10:04:47<br> */ public class DoubleCheckSingleton { /** * Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明<br> * 为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。<br> * volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。<br> * volatile关键字的具体含义需要另行查询,在此不做过多解释。 */ // 使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作。 private static volatile DoubleCheckSingleton dcs; private DoubleCheckSingleton() { } /** * 为了在保证单例的前提下提高运行效率,我们需要对 dcs进行第二次检查,目的是避开过多的<br> * 同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同步获取锁了)。<br> * 这种做法无疑是优秀的,但是我们必须注意一点:<br> * 必须使用volatile关键字修饰单例引用。 <br> * 作者: mht<br> * 时间:2018年3月19日-下午10:14:37<br> */ public static DoubleCheckSingleton getInstance() { // Double-Check idiom if (dcs == null) { synchronized (DoubleCheckSingleton.class) { // 只需在第一次创建实例时才同步 if (dcs == null) { dcs = new DoubleCheckSingleton(); } } } return dcs; } }
注册单例模式:
package design.pattern; import java.util.HashMap; import java.util.Map; /** * 注册单例模式 * <br>类名:RegisterSingleton<br> * 作者: mht<br> * 微信:baozoutianwa<br> * 日期: 2018年3月18日-下午10:03:27<br> */ public class RegisterSingleton<T> { /* 注册表 */ private static Map<String, Object> regMap = new HashMap<>(); // 类加载过程中,静态初始化 static{ Connection conn = new Connection(); UserService us = new UserService(); // 初始化注册表regMap regMap.put(conn.getClass().getName(), conn); regMap.put(us.getClass().getName(), us); } // 私有化构造器 private RegisterSingleton() { System.out.println("注册单例模式构造器..."); } public synchronized static Object getInstance(String key) { if (key == null) return null; try { if (regMap.get(key) == null) { // 这里注意,此Demo是以类名作为key传入map中的,因此,这里的传值也是相关方法,通过类名找到类,然后在进行自动实例化 regMap.put(key, Class.forName(key).newInstance()); } return regMap.get(key); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } }
测试:
package design.pattern; public class testSingleton { public static void main(String[] args) { UserService us = (UserService) RegisterSingleton.getInstance(UserService.class.getName()); Connection conn1 = (Connection) RegisterSingleton.getInstance(Connection.class.getName()); Connection conn2 = (Connection) RegisterSingleton.getInstance(Connection.class.getName()); System.out.println(us); System.out.println(conn1); System.out.println(conn2); System.out.println(conn1 == conn2); } }
运行结果:
design.pattern.UserService@15db9742 design.pattern.Connection@6d06d69c design.pattern.Connection@6d06d69c true
对单例模式应用的理解:
在spring框架中大多数对象的使用都是单例模式的,比如获取用户User对象的方法类UserDao和UserService都是单例的,因为实际上我们真正需要多个对象的是User而并不是获取User的方法类。
这也就充分解释了Spring注解中的@Autowired自动注入对象的实现方式。细心的我们也都可以发现,这些自动注入的对象一般都是实现某种业务的中间类对象,而并不是最终我们需要的对象,因此,为了避免频繁的创建这些“中间件”对象占用不必要的内存空间,都是以单例的形式来创建的。另外,数据库连接对象Connection也一定是单例的。
单例模式的优点:
1.内存中只有一个对象,节省内存空间
2.避免频繁的创建和销毁对象,可以提高性能
3.避免对共享资源的多重占用,简化访问
4.为整个系统提供一个全局访问点
单例模式的应用场景:
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡驱动程序对象常被设计为单例的。实际上,这些应用都或多或少具有资源管理器的功能。
其核心在于为整个系统提供一个唯一的实例,其应用场景包括但不仅限于一下几种:
1.有状态的工具类
2.频繁访问数据库或文件的对象
参考: