什么是单例模式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是单例模式相关的知识,希望对你有一定的参考价值。

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。

通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。

单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。

要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。

使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。

扩展资料:

优缺点:

优点

一、实例控制

单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。

二、灵活性

因为类控制了实例化过程,所以类可以灵活更改实例化过程。

缺点

一、开销

虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。

二、可能的开发混淆

使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。

三、对象生存期

不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用。 

参考资料:百度百科---单例模式

参考技术A java模式之单例模式:
单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例。
特点:
1,一个类只能有一个实例
2,自己创建这个实例
3,整个系统都要使用这个实例
例: 在下面的对象图中,有一个"单例对象",而"客户甲"、"客户乙" 和"客户丙"是单例对象的三个客户对象。可以看到,所有的客户对象共享一个单例对象。而且从单例对象到自身的连接线可以看出,单例对象持有对自己的引用。

Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。在很多操作中,比如建立目录 数据库连接都需要这样的单线程操作。一些资源管理器常常设计成单例模式。
外部资源:譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干个通信端口,系统应当集中管理这些通信端口,以避免一个通信端口被两个请求同时调用。内部资源,譬如,大多数的软件都有一个(甚至多个)属性文件存放系统配置。这样的系统应当由一个对象来管理这些属性文件。

一个例子:Windows 回收站。
在整个视窗系统中,回收站只能有一个实例,整个系统都使用这个惟一的实例,而且回收站自行提供自己的实例。因此,回收站是单例模式的应用。

两种形式:
1,饿汉式单例类
public class Singleton

private Singleton()

//在自己内部定义自己一个实例,是不是很奇怪?
//注意这是private 只供内部调用

private static Singleton instance = new Singleton();

//这里提供了一个供外部访问本class的静态方法,可以直接访问
public static Singleton getInstance()
return instance;



2,懒汉式单例类

public class Singleton

private static Singleton instance = null;

public static synchronized Singleton getInstance()

//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次

//使用时生成实例,提高了效率!
if (instance==null)
instance=new Singleton();
return instance;



第二中形式是lazy initialization,也就是说第一次调用时初始Singleton,以后就不用再生成了。本回答被提问者采纳
参考技术B 定义:
Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。

在很多操作中,比如建立目录 数据库连接都需要这样的单线程操作。

还有, singleton能够被状态化; 这样,多个单态类在一起就可以作为一个状态仓库一样向外提供服务,比如,你要论坛中的帖子计数器,每次浏览一次需要计数,单态类能否保持住这个计数,并且能synchronize的安全自动加1,如果你要把这个数字永久保存到数据库,你可以在不修改单态接口的情况下方便的做到。

另外方面,Singleton也能够被无状态化。提供工具性质的功能,

Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。

我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。

清晨尝鲜,什么是单例模式?


单例模式

简单聊聊,BAT面试高频考点,如何手写一个线程安全的单例模式。

什么是单例模式

保证一个类只有一个实例,并且提供一个访问该全局访问点,保证对象唯一性。

单例优缺点

说两个最核心的优缺点。

  • 优点:节约内存,方便重用
  • 缺点:存在线程安全问题

单例创建方式

  1. 饿汉式:类初始化时,会立即加载该对象,线程天生安全,调用效率高。

    • 私有构造函数
    • 创建全局静态私有对象
    • 创建静态返回对象的方法

注意:不存在线程安全问题为什么呢?

static的成员是在类加载的时候就生成了。饿汉模式在类被初始化时就已经在内存中创建了对象,而不是生成对象的时候,故不存在线程安全问题。

✍上代码:

package com.xianglei;

/**
 * @author xianglei
 */


/**
 *  单例模式 饿汉式   线程安全
 */

public class SingletonDemo01 {
    /**
     *  1.私有构造方法,就是不让你new对象
     */

    private SingletonDemo01(){
     System.out.println("饿汉开始初始化了!!!");
    }
    /**
     * 2.创建对象,饿? 一创建实例就获取对象
     */

    private static final SingletonDemo01 SINGLETON_DEMO_01=new SingletonDemo01();
    /**
     * 3.创建返回对象的方法
     */

    public static SingletonDemo01 getInstance(){
        return SINGLETON_DEMO_01;
    }
    public  static  void  main (String[] args){
        SingletonDemo01 singletonDemo01=SingletonDemo01.getInstance();
        SingletonDemo01 singletonDemo02=SingletonDemo01.getInstance();
        System.out.println(singletonDemo01==singletonDemo02);
    }
}

✍小变种:

package com.xianglei;

/**
 * 饿汉变种
 *
 * @author xianglei
 * @version 1.0
 * @date 2018年12月23日 15:56:20
 */

public class SingletonDemo06 {
    private static SingletonDemo06 instance = null;

    private SingletonDemo06() {

    }
    static {
        instance=new SingletonDemo06();
    }

    public static SingletonDemo06 GetInstance(){
        return instance;
    }
    public static void main(String []args){
        SingletonDemo06 s1=SingletonDemo06.GetInstance();
        SingletonDemo06 s2=SingletonDemo06.GetInstance();
        System.out.println(s1==s2);
    }


}

表面上看起来差别挺大,其实差不多,都是在类初始化即实例化instance。只不过用了一个静态代码块在加载时是第一个加载的知识点。

  1. 懒汉式: 类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能。

    • 私有构造函数
    • 创建全局静态私有对象。
    • 创建静态返回对象的方法,如果为空就创建一个对象,注意处理线程安全问题

✍上代码:

package com.xianglei;

/**
 * @author xianglei
 * 懒汉式 单例模式
 */

public class SingletonDemo02 {
    /**
     * 1. 私有构造方法
     */

    private SingletonDemo02() {

    }
    /**
     * 2.创建静态对象
     */

    private static SingletonDemo02 singletonDemo02;

    /**
     * 3.创建返回对象的方法 注意处理线程安全问题
     */

    public synchronized  static  SingletonDemo02 getInstance() {
        if (singletonDemo02 == null) {     // 判断是否已经存在对象,在这里加同步锁会导致不管是否应该创建了对象每个线程创建的时候会等待别的线程释放锁,从而降低了效率
            singletonDemo02=new SingletonDemo02();
        }
        return singletonDemo02;
    }

    public static void main(String[] args) {
        SingletonDemo02 singletonDemo01=SingletonDemo02.getInstance();
        SingletonDemo02 singletonDemo02=SingletonDemo02.getInstance();
        System.out.println(singletonDemo01==singletonDemo02);
    }
}

注意:

懒汉式是天生线程不安全,为什么呢?如果有多个线程在访问的时候可能会创建多个对象,因为在进行判断可以会被判为空,违背单利的原则。你需要自己加上sychronized关键字。相比于饿汉式效率比较低。

  1. 静态内部方式:结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。

    • 私有构造函数
    • 创建静态内部类 获取实例对象
    • 创建返回对象的静态方法,注意方法没有同步
  • 优势:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。

  • 劣势:需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。

✍上代码:

package com.xianglei;

/**
 * @author xianglei
 * 静态内部类方法 创建单例模式
 */

public class SingletonDemo03 {
    /**
     * 1.私有构造方法
     */

    private SingletonDemo03() {
        System.out.println("初始化..");
    }

    /**
     * 2.创建静态内部类 获取实例对象
     */

    public static class SingletonClassInstance {
        private static final SingletonDemo03 singletonDemo03 = new SingletonDemo03();
    }

    /**
     *  3.创建返回对象的静态方法,注意方法没有同步,
      */


    public static SingletonDemo03 getInstance() {

        return SingletonClassInstance.singletonDemo03;
    }

    public static void main(String[] args) {
        SingletonDemo03 s1 = SingletonDemo03.getInstance();
        SingletonDemo03 s2 = SingletonDemo03.getInstance();
        System.out.println(s1 == s2);
    }

}

注意:

  • 这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,由于在调用 SingletonClassInstance.singletonDemo03 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。所以这种形式,很好的避免了反射入侵。

  • 如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。

  1. 枚举单例: 使用枚举实现单例模式 优点:实现简单、调用效率高,枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞, 缺点没有延迟加载。

    枚举本身是单例的,一般用于项目中定义常量,天生具备JVM保障单例,防止反射攻击。

    • 创建私有构造函数
    • 创建静态枚举,创建构造方法,创建返回对象方法
    • 创建静态返回方法,返回枚举里面返回的对象

✍上代码:

package com.xianglei;

/**
 * @author xianglei
 * 枚举创建单例模式
 */

public class SingletonDemo04 {
    /**
     * 1. 私有化构造方法
     */

    private SingletonDemo04() {

    }

    /**
     * 2.创建枚举方法
     */

    private static enum SingletonDemo {
        /**
         * 枚举元素为单例
         */

        INSTANCE;

        private SingletonDemo04 SingletonDemo04;

        private SingletonDemo() {
            System.out.println("SingletonDemo04");
            SingletonDemo04 = new SingletonDemo04();
        }

        public SingletonDemo04 getInstance() {
            return SingletonDemo04;
        }
    }

    /**
     *3.拿到枚举里面创建的对象
     */

    public static SingletonDemo04 getInstance() {
        return SingletonDemo.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        SingletonDemo04 singletonDemo01 = SingletonDemo04.getInstance();
        SingletonDemo04 singletonDemo02 = SingletonDemo04.getInstance();
        System.out.println(singletonDemo01 == singletonDemo02);

    }

}

注意:使用枚举实现单例模式 优点:实现简单、枚举本身就是单例,由jvm从根本上提供保障!避免通过反射和反序列化的漏洞 缺点没有延迟加载.这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏。

  1. 双重检测锁方式

    在懒汉式的基础上,采用双重锁检验,只阻塞一次

    • 创建私有构造函数
    • 创建静态对象
    • 创建获取返回对象方法 注意双重锁检测

✍上代码:

package com.xianglei;

/**
 * @author xianglei
 * 采用双重检测锁机制实现 单例模式   实质山是基于懒汉模式
 */

public class SingletonDemo05 {
    /**
     * 1.私有化构造方法
     */

    private SingletonDemo05() {

    }

    /**
     * 2.创建静态对象
     */

    private static SingletonDemo05 singletonDemo05;

    /**
     * 3.创建获取返回对象方法 注意双重锁检测
     */

    public static SingletonDemo05 getInstance() {
        if (singletonDemo05 == null) {
            synchronized (SingletonDemo05.class) {
                if (singletonDemo05 == null) {
                    singletonDemo05 = new SingletonDemo05();
                }
            }
        }

        return singletonDemo05;
    }

    public  static  void  main(String [] args){
        SingletonDemo05 singletonDemo01=SingletonDemo05.getInstance();
        SingletonDemo05 singletonDemo02=SingletonDemo05.getInstance();
        System.out.println(singletonDemo01==singletonDemo02);
    }
}

注意:

只有在对象需要被使用时才创建,第一次判断 singletonDemo05 == null 为了避免非必要加锁,当第一次加载时才对实例进行加锁再实例化。这样既可以节约内存空间,又可以保证线程安全。

如何选择单例创建方式

  • 如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。

  • 如果需要延迟加载,可以使用静态内部类或者懒汉式,相对来说静态内部类好于懒汉式。


end




清晨尝鲜,什么是单例模式?
清晨尝鲜,什么是单例模式?

扫描二维码更精彩

清晨尝鲜,什么是单例模式?



点亮 ,告诉大家你也在看


以上是关于什么是单例模式的主要内容,如果未能解决你的问题,请参考以下文章

什么是单例模式

什么是单例模式

spring为啥是单例模式

什么是单例模式?

清晨尝鲜,什么是单例模式?

什么是单例?