设计模式-单例模式

一、单例设计模式

  • 定义保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • 主要解决:一个全局使用的类频繁地创建与销毁。
  • 何时使用:当想控制实例数目,节省系统资源的时候。
  • 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • 关键代码:构造函数是私有的。
  • 应用实例: 1、一个党只能有一个主席。2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
  • 优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。
  • 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

单例模式分为饿汉式和懒汉式

      饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。                

      懒汉式单例模式:在类加载时不初始化。

1. 饿汉模式(线程安全)
  • 饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。
/**
 *饿汉式单例模式(线程安全)
 **/
public class Singleton{
    private static Singleton instance = new Singleton();
    //私有构造方法
    private Singleton(){}
    //静态方法为调用者提供单利对象 
    public static Singleton getInstance(){
        return instance;
    } 
}
  • 类的构造函数定义为 private,保证其他类不能实例化此类,然后提供了一个静态实例并返回给调用者。

  • (1)好处 : 只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。

  • (2)缺点 : 很明显,即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。

  • 这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。但是,如果单例占用的内存比较大,或单例只是在某个特定场景下才会用到,使用饿汉模式就不合适了,这时候就需要用到懒汉模式进行延迟加载。

2. 懒汉模式(线程不安全)
  • 这里使用了懒加载模式,但是却存在致命的问题。当多个线程并行调用getInstance()的时候,就会创建多个实例,即在多线程下不能正常工作。
 /**
  * 懒汉式(线程不安全)
  */
public class Singleton
{
    private static Singleton instance;
    private Singleton(){}
    private static Singleton getInstnce()
    {
        if(instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}
3. 懒汉模式(线程安全)
  • 虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用getInstance()方法,但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。
 /**
  * 懒汉式(线程安全)
  */
public class Singleton
{
    private static Singleton instance;
    private Singleton(){}
    private static synchronized Singleton getInstnce()
    {
        if(instance == null)
        {
            instance = new Singleton();
        }
        return instance;
    }
}
4. 双重检验锁
  • 双重检验模式,是一种使用同步块加锁的方法。又称其为双重检查锁,因为会有两次检查instance == null,一次是在同步块外,一次是在同步块内。
  • 为什么在同步块内还要检验一次。因为可能会有多个线程一起进入同步块外的if,如果在同步块内不进行二次检验的话就会生成多个实例了。
public class Singleton 
{
    private static Singleton instance;
    private Singleton(){}
    private static Singleton getInstance()
    {
        if(instance == null)
        {
            synchronized(Singleton.class)
            {
                if(instance == null)
                {
                    instance == new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 使用volatile的主要原因是:禁止指令重排序优化。在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。
public class Singleton 
{
    private volatile static Singleton instance;
    private Singleton(){}
    private static Singleton getInstance()
    {
        if(instance == null)
        {
            synchronized(Singleton.class)
            {
                if(instance == null)
                {
                    instance == new Singleton();
                }
            }
        }
        return instance;
    }
}
5. 静态内部类(线程安全)
public class Singleton
{
    //静态内部类
    private static class SingletonHolder
    {
        private static final Singleton instance = new Singleton();
    }
    private Singleton(){}

    //第一次调用getInstance方法时,才会去加载SingleHolder类,继而实例化instance
    public static final Singleton getInstance()
    {
        return SingletonHolder.instance;
    }
}
  • 这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
6. 枚举(线程安全)
public class Singleton
{
    //创建枚举默认就是线程安全,而且还能防止反序列化导致重新创建新的对象。
    public enum EasySingleton
    {
        INSTANCE;
    }
}
7. 测试各种单例模式的线程安全
package com.wang.singleton;
public class SingletonDemo implements Runnable 
{
    public static void main(String[] args)
    {
        SingletonDemo[] threads = new SingletonDemo[10];
        for (int i = 0; i < threads.length; i++)
        {
            threads[i] = new SingletonDemo();
        }
        for (int i = 0; i < threads.length; i++) 
        {
            new Thread(threads[i]).start();
        }
    }

    @Override
    public void run() 
    {
        //System.out.println(Singleton.getInstance().hashCode());//饿汉式单例模式(线程安全)
        //System.out.println(Singleton2.getInstance().hashCode());//懒汉式(线程不安全)
        //System.out.println(Singleton3.getInstance().hashCode());//懒汉式(线程安全)
        //System.out.println(Singleton4.getInstance().hashCode());//双重校验锁(线程安全)
        //System.out.println(Singleton5.getInstance().hashCode());//静态内部类(线程安全)
        //System.out.println(Singleton6.getInstance().hashCode());//静态代码块(线程安全)
        System.out.println(Singleton7.INSTANCE.hashCode());//枚举(线程安全)
    }
}













  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值