一、单例模式的定义
单例模式是指确保一个类只有一个实例,并提供该实例的全局访问点。
这样做的好处:有些实例,全局只需要一个就够了,使用单例模式就可以避免一个全局使用的类,频繁的创建与销毁,耗费系统资源。
二、单例模式的设计要素
一个私有构造函数(确保只能单例类自己创建实例)
一个私有静态变量(确保只有一个实例)
一个共有静态函数(给使用者提供调用方法)
简单来说就是,单例类的构造方法不让其他人修改和使用;并且单例类自己只创建一个实例,这个实例,其他人也无法修改和直接使用;然后单例类提供一个调用方法,想用这个实例,只能调用。这样就确保了全局只创建了一次实例。
三、单例模式有6种实现方式(下面详细说明实现及实现的优缺点)
-
懒汉式(线程不安全)
说明:先不创建实例,当第一次被调用时,在创建实例,所以被称为懒汉式。class Singleton { private static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { if (uniqueInstance == null) uniqueInstance = new Singleton(); return uniqueInstance; } }
优点:延迟了实例化,如果不需要使用该类,就不会被实例化,节约了系统资源。
缺点:线程不安全,多线程环境下,如果多个线程同时进入了if(uniqueInstance == null), 若此时还未实例化,也就是 uniqueInstance == null,
那么就会有多个线程执行uniqueinstance = new Singleton(); 就会实例化多个实例。 -
饿汉式(线程安全)
public class Singleton { private static Singleton uniqueInstance = new Singleton(); private Singleton() { } public static Singleton getUniqueInstance() { return uniqueInstance; } }
说明:类加载的时候就立即初始化,并且创建单例对象。【不管需不需要使用这个实例,直接先实例化】,当需要使用的时候,直接调用方法就可以使用了。
优点:提前实例化好了一个实例,避免了线程不安全问题的处理。
缺点:直接实例化好了实例,不再延迟实例化;若系统没有使用这个实例,或者系统运行很久之后才需要使用这个实例,都会操作系统的资源浪费。 -
懒汉式(线程安全)
说明:实现和线程不安全的懒汉式几乎一样,唯一不同的是,在get方法上加了一把锁。如此一来,多个线程访问,每次只能拿到锁的线程能够进入该方法避免了多线程不安全问题的出现。public class Singleton { private static Singleton uniqueInstance; private static singleton() { } private static synchronized Singleton getUinqueInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
优点:延迟实例化,节约了资源,并且是线程安全的。
缺点:虽然解决了线程安全的问题,但是降低了性能。即使已经实例化了,后续不会再出现线程安全问题,但锁还在,每次还是只能拿到锁的线程进入该方法使线程阻塞,等待时间过长。
-
双重检查锁实现(线程安全)
public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { if (uniqueInstance == null) { synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
说明:双重检查相当于改进了线程安全的懒汉式。线程安全的懒汉式的缺点是降低了性能,造成的原因是因为即使已经实例化,依然每次都会有锁。而现在,我们将锁的位置变了,并且多加了一个检查。先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。如果还没有实例化的时候,多个线程进去也没事,因为里面的方法有锁,只会让一个线程进入最内层方法并实例化实例。如此一来,也就第一次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。
使用volatile关键字修饰uniqueInstance实例变量,会禁止JVM的指令重排,可以保证多线程环境下的安全运行。
【volatile:为uniqueInstance分配内存空间;初始化uniqueInstance;将uniqueInstance指向分配的内存地址;】
优点:延迟实例化,节约了资源;线程安全;相对于线程安全的懒汉式,性能提高了。
缺点:volatile关键字,对性能也有一些影响。 -
静态内部类实现(线程安全)
public class Singleton { private Singleton() { } private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getUniqueInstance() { return SingletonHolder.INSTANCE; } }
说明:当外部类Singleton被加载时,静态内部类SingletonHolder并没有被加载进内存。当调用getUniqueInstance()方法时,会运行return SingletonHolder.INSTANCE; 触发了SingletonHolder.INSTANCE,此时静态内部类SingletonHolder才会被加载进内存,并且初始化INSTANCE实例,而且JVM会确保INSTANCE只被实例化一次。
优点:延迟实例化,节约了资源;且线程安全;性能也提高了。 -
枚举类实现(线程安全)
public enum Singleton { INSTANCE; //添加自己需要的操作 public void doSomeThing() { } }
说明:默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。
优点:写法简单,线程安全,防止反射和反序列化调用。
防止反序列化
序列化:把java对象转换为字节序列的过程;
反序列化:通过这些字节序列在内存中新建Java对象的过程;
说明:反序列化将一个单例实例对象写到磁盘在读回来,从而获得了一个新的实例。
四、单例模式的应用场景
应用场景举例:
- 网站计数器
- 应用程序的日志应用
- web项目中的配置对象的读取
- 数据库的连接池
- ..............
应用场景总结:
- 频繁实例化然后又销毁的对象,使用单例模式可以提高性能。
- 经常使用的对象,但实例化时耗费时间或者资源多,如数据库连接池,使用单例模式,可以提高性能,降低资源损坏。
- 使用线程池之类的控制资源时,使用单例模式,可以方便资源之间的通信。