浅谈Java中单例模式的几种应用

目录

 

浅谈Java中单例模式的几种应用

第一种:懒汉式

第二种:饿汉式

第三种:双重检索式

第四种:注册登记式

第五种:内部类形式


浅谈Java中单例模式的几种应用

日常开发中,为了提高我们系统中对象的复用性,大多采用单例模式的写法,以达到在系统中重复利用对象的目的。下面小编为大家简单介绍几种日常开发中常见的单例模式写法,以供参考和使用!如果有疑问大家可以留言公共讨论,共同学习进步。

第一种:懒汉式

懒汉的方式正如字面意思,可以通俗的理解为:来活了我再开始干活!下面看下懒汉式的具体写法。

public class LazySingletonPattern {
    /**
     * 私有对象
     */
    private static UserModel userModel;

    /**
     * 获取唯一实例
     *
     * @return
     */
    public static synchronized UserModel getInstance() {
        if (userModel == null) {
            userModel = new UserModel();
        }
        return userModel;
    }
}

从上面代码分析得知,程序加载时userModel对象是空的,当我们调用getInstance方法时,首先判断内存中是否有userModel对象,如果没有则创建一个,之后静态的userModel对象会一直驻留在外面本机的内存中。需要注意的是这里一定要加上synchronized关键字,来保证getInstance方法是线程安全的,否则可能导致的结果就是内存中会创建多个userModel对象。

第二种:饿汉式

饿汉式相对于其他方式是最简单也是最暴力的一种方式,如下代码:

public class HungrySingletonPattern {
    /**
     * 初始化静态UserModel
     */
    private static UserModel userModel = new UserModel();

    /**
     * 唯一实例
     * @return
     */
    public static UserModel getInstance(){
        return userModel;
    }
}

代码中直接实例化UserModel对象,也就是在项目启动的时候就为我们创建好了一份userModel对象,这样的优点在于静态对象本身就是单例的,我们在使用的时候可以不考虑线程安全问题。缺点也是显而易见的,在程序初始化时就要为我们创建好这些对象放入到内存中,造成了空间的浪费。

第三种:双重检索式

双重检索的方式,可以说是在性能和安全两个角度找了一个平衡点,可以理解为懒汉式单例模式的加强版,既考虑性能又考虑安全性的问题。

public class DoubleCheckSingletonPattern {
    /**
     * 静态UserModel
     */
    private static UserModel userModel;

    /**
     * 双重检索单例方式
     *
     * @return
     */
    public static UserModel getInstance() {
        if (userModel == null) {
            synchronized (DoubleCheckSingletonPattern.class) {
                if (userModel == null) {
                    userModel = new UserModel();
                }
            }
        }
        return userModel;
    }
}

双重检查锁的优势就在于会优先验证一次userModel对象是否存在值,而不是像懒汉式一样优先加锁,这样导致了性能的浪费。虽说synchronized在1.6之后得到了很好的优化,但是在多线程竞争下依然不排除性能的浪费。双重检索可以降低锁的浪费,也同时保证了线程的安全。

隐患补充:这里隐藏着一个问题,当两个线程进来后,先获取锁的线程开始创建线程的同时,第二个线程进入判断,因为构造对象的过程可能会比较长,这时第一个线程还未完成对象的完整创建,但第二个线程会拿到一个不是null的值,从而认为已经构造完成,导致返回的类并非是一个完整的对象。解决方案可以通过volatile来解决这个问题,完美的解决了这个问题的还是推荐使用内部类的创建方式。

第四种:注册登记式

注册登记式更适合多实例场景的管理,当下最火的Spring框架中IOC容器就采用的这种方式来管理我们系统中的Bean对象,小编这里只做简单的示例,相对于Spring中的要简单的多,不喜勿喷。

public class RegistrySingletonPattern {
    /**
     * Map容器
     */
    private final static Map<String, Object> objectsMap = new ConcurrentHashMap<>();

    /**
     * 获取实例
     *
     * @return
     */
    public static synchronized Object getInstance() {
        String key = "&userModel";
        if (!objectsMap.containsKey(key)) {
            objectsMap.put(key, new UserModel());
        }
        return objectsMap.get(key);
    }
}

上面代码得知我们创建的单例对象全部保存在Map容器中,由Map容器统一管理我们的单例对象。

第五种:内部类形式

内部类可以说是比较有创意的一种方式了,避免资源的直接浪费,也同时保证了单例。

public class InnerClassSingletonPattern {

    static {
        System.out.println("父类加载了");
    }

    /**
     * 获取实例
     *
     * @return
     */
    public static UserModel getInstance() {
        return UserModelSingleton.userModel;
    }

    /**
     * 内部类
     */
    static class UserModelSingleton {
        static final UserModel userModel = new UserModel();

        static {
            System.out.println("子类加载了");
        }
    }
}

内部类不同于饿汉式和懒汉式,也算是集成了这两种方式的优点,程序启动后并不会初始化内部类,而当外部类调用内部类的方法时,才会初始化内部类的UserModel实例,保证在不浪费资源的情况下达到的单例模式的应用。

 

小结:

小编这里只是例举了几种在工作中常用的几种写法,有问题的地方还请大家及时指出,免的误导了其他同学。本文涉及到其他技术点,这里不做扩展讲解,大家可以去了解下多线程多面的知识,小编也还有许多需要学习的地方,还请大家多多指点。

发布了4 篇原创文章 · 获赞 12 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览