单例模式的定义与特点
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式有 3 个特点:
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点。
单例模式的优点和缺点
单例模式的优点:
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
饿汉模式
public final class HungrySingle { private static HungrySingle instance=new HungrySingle(); private HungrySingle(){} public static HungrySingle getInstance() { return instance; } }
将类的构造函数定义模式为private,让其他类无法实例化此类,然后提供一个静态方法给调用者。好处是类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了线程同步的问题,坏处是在没用的情况下也会被创建,浪费了内存。如果在初始化阶段就能用到,占用内存也比较小,适合用这种。
懒汉模式
public final class LazySingle { private static LazySingle instance=null; private LazySingle(){} public static LazySingle getInstance(){ if (null==instance){ instance=new LazySingle(); } return instance; } }
好处是只有在需要时才会被创建,如果单例已经创建,再次调用也不会创建新的对象。适用于使用次数较少,并且消耗的单例资源较多的情况,就可以按需创建。但这里的懒汉模式没考虑线程的安全问题,可能会存在多个线程并发调用方法的问题,所以下面有解决办法:
public final class LazySingleSecondChoice { private static LazySingleSecondChoice instance=null; private LazySingleSecondChoice(){} public static synchronized LazySingleSecondChoice getInstance() { if (null==instance){ instance=new LazySingleSecondChoice(); } return instance; } }
但同步锁也会有另一个缺点,就是增加锁竞争,带来性能开销,导致性能下降,会降低单例模式的性能。所以为了解决找个问题,我们可以将同步锁放在if里面,因为除了第一次创建的时候为null,其他时候基本都不会为null的情况。只有当if条件判断为null时,才会创建实例,所以可以将同步锁放在里面,减少竞争
public final class LazySingleThirdChoice { private static LazySingleThirdChoice instance= null; private LazySingleThirdChoice(){} public static LazySingleThirdChoice getInstance() { if (null==instance){ synchronized (LazySingleThirdChoice.class){ instance=new LazySingleThirdChoice(); } } return instance; } }
但是现在依然会创建多个实例。这是因为当多个线程进入到 if 判断条件里,虽然有同步锁,但是进入到判断条件里面的线程依然会依次获取到锁创建对象,然后再释放同步锁。所以我们还需要在同步锁里面再加一个判断条件 ,双重校验锁:
public class DoubleSynchronizedLazySingle { private static DoubleSynchronizedLazySingle instance=null; private DoubleSynchronizedLazySingle(){} public static DoubleSynchronizedLazySingle getInstance() { if (null==instance){ synchronized (DoubleSynchronizedLazySingle.class){ if (null==instance) // 判断两次 instance=new DoubleSynchronizedLazySingle(); } } return instance; } }
静态内部类
利用了类加载机制来保证只创建一个实例,因此不会存在多线程的问题。又因为使用了内部类的方法,所以不会在JVM的时候就进行创建,只有当调用内部类方法时,才会创建。推荐这种方法,同时保证了延迟加载和线程安全
public final class StaticSingle { public List<String> list=null; private StaticSingle(){} public static class InnerSingle{ private static StaticSingle instance=new StaticSingle(); } public static StaticSingle getInstance(){ return InnerSingle.instance; } }
希望以上能帮助到你在什么时候选择什么方式,如果在项目启动后就一定会需要,可以使用饿汉式,如果是工具类,希望在需要的时刻在进行创建,建议懒汉式,避免占用内存资源