设计核心
一个类只有一个对象实例,且由其自行创建,并提供一个访问它的全局访问点。
七种写法
饿汉模式
1 | public class A{ |
这一写法是在类的加载时完成的初始化,避免了多线程难以同步的问题,但是无论这一实例需不需要使用都对其进行了装载,使用的内存多,但是效率高。
顺带一提,其名字很有意思,饿汉模式中的饿汉体现在获取实例的迅速,因为饥饿所以要迅速的得到补充,所以一开始就把实例准备好。
懒汉模式
1 | public class A{ |
在类的加载时并不着急去初始化,而是等着需要调用的时候,根据需要进行初始化实例,但是效率不会太高,毕竟临时初始化需要时间,而且会有线程的同步问题,在多线程中,多个线程同时对其进行调用,在判断是否已经初始化过的时候,很难进行同步,产生多个实例。但是如果没有对其的调用的话,就不需要进行初始化,节约了内存空间。
这个名字也很贴切,懒汉不急,什么时候需要使用时,它才慢悠悠的进行准备,如果一直没有使用需求的话,它就一直这么懒下去。
懒汉模式(保证线程安全)
1 | public class A{ |
这个写法与上一个的写法没有什么不同,唯一一点就是在获取实例的方法处加了一个锁用于同步,每次在使用这个函数之前先检查有没有别的线程在使用该函数,如果别的线程在使用则该函数,则会对这一函数加锁,使其等待其访问结束后才能调用,保证了多个线程之间对实例初始化的同步。但是这一机制导致每次调用实例时都需要对其进行检查,有很大的同步开销,所以不建议这一写法。
双重检查模式
1 | public class A{ |
在获取实例时对实例进行了两次的检查确认实例没有初始化后进行实例操作,其中第一次的检查是防止不必要的同步操作。这一方式保证了实例在第一次获取时进行实例化,效率很高,但是实例化需要时间,对于高并发操作还有一定影响,但是已经解决了同步问题和多余的资源消耗,并且只是第一次实例化时存在延迟,还是存在一定问题。
静态内部类
1 | public class A{ |
在加载A类时不需要加载AInClass,只有第一次调用A的实例时,AInClass才会被加载,同时实例化。这种方法保证了线程同步性和单例的唯一性,同时不需要使用时不加载AInClass,合理的使用内存,同时也保证了效率,推荐使用。
枚举模式
1 | public enum A { |
枚举模式十分简单只需要使用枚举类型即可,解决了多线程同步的问题,而且系统中只保存了这一个实例,节省了内存空间,但是枚举对象是jdk1.5之后添加的,操作上可能会有理解上的问题。
使用容器实现
1 | public class InstanceMap{ |
这种方式充分的使用了HashMap的特点,同一个键对应一个实例对象,保证了单一实例,同时可以管理多种不同类型的单例,并且使用统一的类进行管理,条理清晰。
小结
单例模式的几种写法各自有对应的特点,我们可以根据自己项目的需求选择合适的使用方式,来控制实例化对象对资源的消耗。