单例
设计模式 (Singleton pattern)
本节只涉及到23种设计模式中最简单的一种,单例设计模式。
主要阐述的是比较经典的三种构建方式:懒汉式,饿汉式
1.了解什么是单例设计模式?
一个类有且仅有一个实例,并且自行实例化向整个系统提供。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。
2.为什么要保证系统中,应用模式的类只有一个实例?
首先我们先了解一下单例模式的特点:
1. 单例类只能有一个实例。
2. 单例类必须自己创建自己的唯一实例。
3. 单例类必须可以给所有其他对象提供这一实例。
简而言之,单例模式确保系统中的某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
百科解释:(内存,用户体验)对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解, 不知道哪一个才是真实 的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
具体应用场景 在浏览网站时,在线人数显示这样的功能,相信在大家熟悉的不能再熟悉了。
思路:通常我们是把当前的在线人数存放到一个内存、文件或者数据库中,每次用户登录的时候,就会马上从内存、文件或者数据库中取出,在其基础上加1后,作为当前的在线人数进行显示,然后再把它保存回内存、文件或者数据库里,这样后续登 录的用户看到的就是更新后的当前在线人数;同样的道理,当用户退出后,当前在线人数进行减1的工作。
但是如果多个用户同时登录,那么在这个时刻,通过计数器取到的在线人数是相同的,于是他们使用各自的计数器加1后存入文件或者数据库。这样操作后续登陆的用户得到的在线人数,与实际的在线人数并不一致。
我们通常解决的办法就是把设定成一个全局变量,所有的用户操作同一份数据,就避免了数据保存多份数据导致的内存消耗,以及数据不一致的情况。这就是我们所说的单例模式的其中的一种应用。
3. 单线程环境下 单例模式的简单代码实现
public class Singleton { private Singleton (){} //构造函数私有化 public static Singleton getInstance() { //静态方法调用,生产实例 Singleton instance = new Singleton(); return instance; } }
根据创建方式不同,通常又分为:饿汉式和懒汉式
饿汉模式,它的特点是加载类的时候比较慢,但运行时获得对象的速度比较快。它从加载到应用结束会一直占用资源。
// 饿汉式单例 public class Singleton1 { // 指向自己实例的私有静态引用,主动创建 private static Singleton1 singleton1 = new Singleton1(); // 私有的构造方法 private Singleton1(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton1 getSingleton1(){ return singleton1; } }
懒汉模式,它的特点是运行时获得对象的速度比较慢,但加载类的时候比较快。它在整个应用的生命周期只有一部分时间在占用资源。
// 懒汉式单例 public class Singleton2 { // 指向自己实例的私有静态引用,不主动创建 private static Singleton2 singleton2 = null; // 私有的构造方法 private Singleton2(){} // 以自己实例为返回值的静态的公有方法,被调用时创建实例 public static Singleton2 getSingleton2(){ singleton2 = new Singleton2(); return singleton2; } }
注意:这两种构建方式,没有明确的谁优谁劣。根据在不同应用场景,采取合适的构建方式。这两种模式对于初始化较快,占用资源少的轻量级对象来说,没有多大的性能差异,选择懒汉式还是饿汉式都没有问题。但是对于初始化慢,占用资源多的重量级对象来说,就会有比较明显的差别了。所以,对重量级对象应用饿汉模式,类加载时速度慢,但运行时速度快;懒汉模式则与之相反,类加载时速度快,但运行时第一次获得对象的速度慢。
4. 多线程环境下 单例模式的简单代码实现
单线程环境下不管是懒汉还是恶汉都没有问题,根据应用场景选择合适的构建方式。多线程环境中,就会出现线程安全问题。
对于传统的懒汉式单例模式,是非线性安全的。
// 懒汉式单例 public class Singleton3 { // 指向自己实例的私有静态引用,不主动创建 private static Singleton3 singleton3 = null; // 私有的构造方法 private Singleton3(){} // 以自己实例为返回值的静态的公有方法,被调用时创建实例 public static Singleton3 getSingleton3(){
//多个线程操作 if(singleton3==null)... 多个线程满足条件时就可能会产生多个实例 if(singleton3==null) singleton3 = new Singleton3(); return singleton3; } }
懒汉式变种(线性安全,但执行效率低)
// 懒汉式单例
public class Singleton4 {
// 指向自己实例的私有静态引用,不主动创建
private static Singleton4 singleton4 = null;
// 私有的构造方法
private Singleton4(){}
// 以自己实例为返回值的静态的公有方法,被调用时创建实例,
public static synchronized Singleton4 getSingleton4(){
//多个线程操作 if(singleton4==null)... 多个线程满足条件时就可能会产生多个实例
if(singleton4==null)
singleton4 = new Singleton4();
return singleton4;
}
}
对于传统的饿汉式单例模式,天生就是线性安全的.
这种方式比较常用,没有加锁,执行效率会提高。但容易产生垃圾对象。类加载时就初始化,也会浪费内存。
// 饿汉式单例 public class Singleton5 { // 指向自己实例的私有静态引用,主动创建 private static Singleton5 singleton5 = new Singleton5(); // 私有的构造方法 private Singleton5(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton5 getSingleton5(){ return singleton5; } }
双重检锁(懒汉式优化版)
public class Singleton7 { // 指向自己实例的私有静态引用,不主动创建 private volatile static Singleton7 singleton7 = null; // 私有的构造方法 private Singleton7() { } // 以自己实例为返回值的静态的公有方法,被调用时创建实例, public static Singleton7 getSingleton7() { //多个线程操作 if(singleton6==null)... 多个线程满足条件时就可能会产生多个实例 if (singleton7 == null) { synchronized (Singleton7.class) { if (singleton7 == null) { singleton7 = new Singleton7(); } } } return singleton7; } }
注意: 关键字 volatile
volatile :可以保证多线程下的可见性;
读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。
写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。