一:单例模式诞生的背景
1:背景
平常我们在用Java创建一个读取某些配置文件或者实现某些功能的类的时候,往往在每一次使用时都new实例化一次,这样在整个系统中,会存在过多的实例化对象,占用内存空间,浪费系统资源,尤其是读取某些固定的配置文件时,往往只需要一个实例就够了,即:在一个系统的运行期间,某个类只需要一个类实例就可以,那么应该怎么实现?
二:单例模式
解决上面的问题的一个很好的办法就是使用单例模式。
1:单例模式定义
Singletom:单例模式;定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
2:应用单例模式解决问题思路
细细分析上面的问题,一个类能够被创建多个实例,问题的根源在于类的构造方法是公开的,也就是可以让类的外部来通过构造方法创建多个实例。即只要类的构造方法能让类的外部访问,就没有办法去控制外部来创建这个类的实例个数。
要想控制一个类只被创建一个实例,那么首要的问题就是要把创建实例的权限回收回来,让类自身来负责自己类实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法,这就是单例模式的实现方式。
3:单例模式的结构和说明
Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
4:单例模式种类说明及
首先从大的方面来说:单例模式有恶汉模式和懒汉模式。其中恶汉模式线程安全,懒汉模式线程不安全。
所谓恶汉模式即创建对象实例的时候比较着急,于是在装载类的时候就创建对象实例。
private static Singleton uniqueInstance = new Singleton();
/**
* 恶汉式单例实现的示例
*
* @author Peter
*/
public class Singleton {
// 1:私有化构造器,可以在内部控制创建实例的数目
private Singleton() {
}
//4: 定义一个变量来存储创建好的类实例,直接在这里创建类实例,只能创建一次 5:因为变量在静态方法中使用,故需要加static
private static Singleton uniqueInstance = new Singleton();
// 2:定义一个方法来为客户端提供类实例(3:这个方法需要定义成类方法,故需要加static)
public static Singleton getInstanct() {
// 5:直接使用已经创建好的实例
return uniqueInstance;
}
// 单例可以有自己的操作
public void singletonOperation() {
}
// 单例可以有自己的属性
private String singletonData;
// 让外部通过这些方法来访问属性的值
public String getSingletonData() {
return singletonData;
}
}
所谓懒汉模式即创建对象实例的时候不着急,会一直等到马上要使用对象实例的时候才会创建,因此在装载对象的时候不创建对象实例。而是要等到第一次使用的时候,才去创建实例,也就是在getInstance方法里面去判断和创建。
private static Singleton uniqueInstance = null;
/**
* 懒汉式单例实现的示例
*
* @author Peter
*/
public class Singleton {
// 1:私有化构造器,可以在内部控制创建实例的数目
private Singleton() {
}
// 4:定义一个变量来存储创建好的类实例 5:因为这个变量需要在静态方法中使用,故需要加static
private static Singleton uniqueInstance = null; //体现了缓存的思想
// 2:定义一个方法来为客户端提供类实例 3:这个方法需要定义成类方法,即需要加static
public static Singleton getInstance() {
// 6:判断存储实例的变量是否有值,
if (uniqueInstance == null) {
// 6.1如果没有,就创建一个类实例,并把值赋值给存储类实例的变量
uniqueInstance = new Singleton(); //体现了延迟加载的思想
}
// 6.2如果有值,就直接使用
return uniqueInstance;
}
// 单例可以有自己的操作
public void singletonOperation() {
}
// 单例可以有自己的属性
private String singletonData;
// 让外部通过这些方法来访问属性的值
public String getSingletonData() {
return singletonData;
}
}
三:单例模式讲解
1:单例模式的功能
单例模式是用来保证这个类在运行期间只会被创建一个类实例,另外,单例模式还提供了一个全局唯一访问这个类实例的访问点,就是getInstance方法。不管采用懒汉式还是恶汉式的实现方式,这个全局访问点是一样的。
对于单例模式而言,不管采用何种实现方式,它都是只关心类实例的创建问题,并不关心具体的业务功能。
2:单例的范围
观察上面的实现可以知道,目前Java里面实现的单例是一个虚拟机的范围。因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载恶汉式实现单例类的时候就会创建一个类的实例对象。
这就意味着如果一个虚拟机里面有很多个ClassLoader,而且这些ClassLoader都装载某个类的话,就算这个类是单例,它也会产生很多个实例。当然,如果一个机器上有多个虚拟机,那么每个虚拟机里面都应该至少有一个这个类的实例,也就是说整个机器上有很多这个实例,更不会是单例了。
3:单例模式的命名
一般建议单例模式的方法命名为getInstance(),这个方法返回的类型肯定是单例类的类型。getInstance()方法也可以有参数,这些参数可能是创建类实例所需要的参数,当然大多数情况下是不需要的。
单例模式的名称有:单例、单件、单体等,只是翻译的问题而已。
注:static变量在类加载的时候进行初始化;多个实例的static变量会共享同一块内存区域。
4:单例模式示意图
懒汉式:
恶汉式:
5:单例模式优缺点
(1)时间和空间
懒汉式是典型的时间换空间(每次获取实例都需要进行判断,没有才进行实例化,浪费时间,当然如果一直没人用则节约空间),恶汉式是典型的空间换时间(当类装载的时候就会创建,不管你用不用,先创建出来,每次调用不进行判断,节约时间)。
(2)线程安全性问题
恶汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的;懒汉式是线程不安全的,因为不加同步的懒汉式是线程不安全的,
6:如何实现懒汉式线程安全问题
(1)加上同步关键字synchronized即可
public class Singleton {
private Singleton(){
}
private static Singleton instance = null;
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
但是synchronized会降低整个访问的速度,而且每次还需要进行判断。
(2)双重检查加锁
双重检查加锁:并不是每次进入getInstance方法都需要进行同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,只需要同步一次,从而减少了多次在同步情况下进行判断所浪费的时间。
双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
public class Singleton {
private Singleton(){
}
//对保存的变量添加volatile修饰
private volatile static Singleton instance = null;
public static Singleton getInstance(){
//先检查是否存在,如果不存在则进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized (Singleton.class) {
//再次检查是否存在,如果不存在才真正的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
注:由于volatile关键字很可能屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。即,虽然可以使用“双重检查加锁”机制来实现线程的单例,但并不建议大量采用,可以根据情况选用。
(3)通过内部类实现线程安全的懒汉式
类级内部类:有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
类级内部类相当于其外部类的static成分,它的对象和外部类对象间不存在依赖关系,因此可以直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。
在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况下,JVM已经隐含的为你执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况为:由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时;访问final字段时;在创建线程之前创建对象时;线程可以看见它将要处理的对象时。
public class Singleton {
//私有化构造方法
private Singleton(){
}
//类级的内部类,即静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,
//而且只有在被调用的时才会装载,从而实现了延迟加载
private static class SingletonHolder{
//静态初始化器,有JVM保证线程安全
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行了一个域的访问,因此延迟初始化并没有增加任何访问成本。
(4)利用枚举实现懒汉式单例
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return EnumSingle.INSTANCE.getInstance();
}
private static enum EnumSingle{
INSTANCE;
private Singleton singleton;
private EnumSingle(){
singleton = new Singleton();
}
public Singleton getInstance(){
return singleton;
}
}
}
(5)利用缓存实现懒汉式单例
public class Singleton {
//私有化构造方法
private Singleton(){
}
//定义一个默认的key值,用来标识在缓存中的存放
private static final String DEFAULT_KEY = "One";
//缓存实例的容器
private static Map<String, Singleton> map = new HashMap<String, Singleton>();
public static Singleton getInstance(){
//先从缓存中获取
Singleton instance = map.get(DEFAULT_KEY);
if(instance == null){
//如果没有,就新建一个,然后设置到缓存
instance = new Singleton();
map.put(DEFAULT_KEY, instance);
}
return instance;
}
}
(6)生成指定数目的单例
public class OneExtend {
//私有化构造方法
private OneExtend(){}
//定义一个缺省key值的前缀
private final static String DEFAULT_PREKEY = "Cache";
//缓存实例的容器
private static Map<String ,OneExtend> map = new HashMap<String, OneExtend>();
//用来记录当前使用的第几个实例,到了控制的最大数目,就返回从1开始
private static int num = 1;
//定义控制实例的最大数目
private final static int NUM_MAX = 3;
public static OneExtend getInstance(){
String key = DEFAULT_PREKEY + num;
OneExtend oneExtend = map.get(key);
if(oneExtend == null){
oneExtend = new OneExtend();
map.put(key, oneExtend);
}
num++;
if(num > NUM_MAX){
num = 1;
}
return oneExtend;
}
}
注:线程不安全的。
四:小思单例模式
单例模式的本质是控制实例数目。
当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点去访问它时,可以选用单例模式。
很多模式都可以使用单例模式,只要这些模式中的某个类,需要控制实例为一个的时候,就可以很自然地使用上单例模式。比如抽象工厂方法中的具体工厂类就通常是一个单例。