单例模式(Java)

一:单例模式诞生的背景

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;
	}
}

注:线程不安全的。

四:小思单例模式

单例模式的本质是控制实例数目

当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点去访问它时,可以选用单例模式。

很多模式都可以使用单例模式,只要这些模式中的某个类,需要控制实例为一个的时候,就可以很自然地使用上单例模式。比如抽象工厂方法中的具体工厂类就通常是一个单例。

        

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单例模式是一种设计模式,它确保一个只有一个实例,并提供全局访问点。 在Java中,可以通过以下方式实现单例模式: 1. 懒汉单例模式 ```java public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` 这种方式下,只有在第一次使用getInstance()方法时才会创建单例对象。 2. 饿汉式单例模式 ```java public class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } ``` 这种方式下,单例对象在加载时就已经创建好,因此可以保证线程安全。 3. 双重校验锁单例模式 ```java public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` 这种方式下,通过双重校验锁实现了懒加载和线程安全。 4. 静态内部单例模式 ```java public class Singleton { private Singleton() {} private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } ``` 这种方式下,通过静态内部实现了懒加载和线程安全。当Singleton被加载时,静态内部SingletonHolder不会被加载,只有在第一次调用getInstance()方法时才会加载SingletonHolder,从而实例化Singleton对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值