设计模式----单例模式

设计模式总结

一、单例模式

  • 类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法),而不提供new。

实现方式:

  1. 饿汉式

    • 静态常量:

      class Singleton {
      	//1. 构造器私有化, 外部能new
      	private Singleton() {
      	}
          
      	//2.本类内部创建对象实例
      	private final static Singleton instance = new Singleton();
      	
      	//3. 提供一个公有的静态方法,返回实例对象
      	public static Singleton getInstance() {
      		return instance;
      	}
      }
      
    • 静态代码块:

      class Singleton {
      	//1. 构造器私有化, 外部能new
      	private Singleton() {
      	}
      	
      	//2.本类内部创建对象实例
      	private  static Singleton instance;
      
      	static { // 在静态代码块中,创建单例对象
      		instance = new Singleton();
      	}
      	
      	//3. 提供一个公有的静态方法,返回实例对象
      	public static Singleton getInstance() {
      		return instance;
      	}
      }
      
      • 其中类构造器私有化是为了保证外部代码不能通过new的方式去创建对象实例,把对象的实例化全权交给获取实例化的方法,这样能够方便构建单例模式。
      • 优点:写法比较简单,在类装载的时候就完成实例化。避免了线程同步问题。
      • 缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
      • 结论:这种单例模式可用,但可能造成内存浪费。如果在保证该类实例一定会被使用到的情况下直接可以使用饿汉式。
  2. 懒汉式

    • 线程不安全:

      class Singleton {
      	private static Singleton instance;
      	private Singleton() {
      	}
      	
      	//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
      	//即懒汉式
      	public static Singleton getInstance() {
      		if(instance == null) {
      			instance = new Singleton();
      		}
      		return instance;
      	}
      }
      
      • 这种创建模式可以在调用创建实例方法时才创建对象实例,避免了在类装载时可能造成的内存浪费。
      • 但这种方式确是线程不安全的,在多线程下,可能会发生当一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
      • 结论:在实际开发中,这种方式不可用
    • 线程安全,同步方法:

      class Singleton {
      	private static Singleton instance;
      	
      	private Singleton() {}
      	
      	//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
      	//即懒汉式
      	public static synchronized Singleton getInstance() {
      		if(instance == null) {
      			instance = new Singleton();
      		}
      		return instance;
      	}
      }
      
      • 给获取实例的方法加上了synchronized同步后,一个线程在使用方法时,其他线程将被阻塞需要等待,解决了线程不安全问题。
      • 缺点:效率太低,每个线程在想获得类的实例时,执行getInstance()方法都要进行同步。而其实这个方法只用执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。
      • 结论:不推荐使用这种方式。
  3. 双重检查

    class Singleton {
        private static volatile Singleton instance;
    
        private Singleton() {}
    
        //提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
        //同时保证了效率, 推荐使用
        public static synchronized Singleton getInstance() {
            if(instance == null) {
                synchronized (Singleton.class) {
                    if(instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    • volatile:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了变量的值,这新值对其他线程来说是立即可见的。
      instance采用 volatile 关键字修饰也是很有必要的, instance= new Singleton(); 这段代码其实是分为三步执行:
      a.为 instance分配内存空间
      b.初始化 instance
      c.将 instance指向分配的内存地址
      但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 a->c->b。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现 instance不为空,因此返回 instance,但此时 instance还未被初始化。

    • 当有多个线程同时进入第一个if判断中时,由于synchronized (Singleton.class)保证了只有一个线程可以进入,这时最先进来的线程会进行第二个if判断创建一个实例,而后面的已经进入第一个if的线程,会依次进行第二个if判断,都会失败。至此以后的所有线程都会在第一个if判断处失败直接return创建好的实例。

    • 结论:线程安全,效率较高。推荐使用这种单例设计模式。

  4. 静态内部类

    class Singleton {
    	private static volatile Singleton instance;
    	//构造器私有化
    	private Singleton() {
    	}
    	
    	//写一个静态内部类,该类中有一个静态属性 Singleton
    	private static class SingletonInstance {
    		private static final Singleton INSTANCE = new Singleton(); 
    	}
    	
    	//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
    	public static synchronized Singleton getInstance() {
    		return SingletonInstance.INSTANCE;
    	}
    }
    
    • 因为静态内部类在外部类被装载时并不会立即实例化,而是在调用getInstance()方法,才会装载SingletonInstance类,从而完成Singleton的实例化。避免了在类装载时可能造成的内存浪费。
    • 类的静态属性只会在第一次加载类的时候初始化,又因为JVM在加载类时,别的线程是无法进入的。保证了线程的安全。
    • 结论:推荐使用。
  5. 枚举

    enum Singleton {
    	INSTANCE; //属性
    	public void sayOK() {
    		System.out.println("ok~");
    	}
    }
    
    • 不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
    • 结论:推荐使用。
  6. 单例模式在JDK 应用的源码

    image-20210504204130379

    • java.lang.Runtime是经典的单例模式(饿汉式)。

注意事项

  • 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(如数据源、session工厂等)。

  • 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

  • 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(如数据源、session工厂等)。

  • 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值