设计模式之单例模式

1 引子

单例模式是指某个类有唯一的实例。
最常见的获取单例的方法有两种:饿汉式和懒汉式。
懒汉式单例模式:

public class Single1 {
	private static Single1 single1;   
	private Single1(){}         
	public static Single1 getInstance(){
		if (single1 == null){
			single1 = new Single1();
		}
		return single1;
	}
}

懒汉式单例模式,意思是调用getInstance()后才会生成对象,并返回对象,即需要的时候才会生成一个对象。
注意:getInstance()方法为static修饰的静态方法,使得方法属于类,通过[Single1.]便可调用getInstance(),避免其他类中创建Single1对象,另外private修饰的构造方法表明,只能在该类中生成对象。
懒汉式单例模式的特点是延迟加载,在需要该对象的时候才会生成对象,节省了不必要的空间;但该模式也存在一个致命的缺点,后面会讲到。

饿汉式单例模式:

public class Single2 {
	private static final Single2 SINGLE2 = new Single2();
	private Single2(){}
	public static Single2 getInstance(){
		return Single2.SINGLE2;
	}
}

饿汉式单例模式,意思是类加载就会立马生成一个对象,为了保证只能生成一个该类的对象,所以用关键字final修饰,即不可变。当需要该对象的时候,调用getInstance()方法即可返回该唯一的对象。
饿汉式单例模式的特点:饿汉式故名思议好比处于饥饿状态,类加载后立马生成该类的实例,不管需不需要;这容易造成空间的浪费,相比懒汉式单例,饿汉式单例不可用。

饿汉式单例模式还可以换一种方法来实现,即便不推荐使用;饿汉式静态模块单例模式

public class Single3 {
	private Single3(){}
	private static Single3 single3;
	static
	{
		single3 = new Single3();
	}
	public static Single3 getInstance(){
		return single3;
	}
}

饿汉式静态模块单例模式的本质跟上面的饿汉式单例模式一样,只不过把直接生成单例的过程放到了静态代码块,即在类加载的过程中会执行静态代码块的内容,即生成了single3实例。
注意:这里的single3引用没有final关键字,因为一个类加载只会执行一次静态代码块,所以能确保single3的实例是唯一的。

上面说到懒汉式单例模式,即Single1有一个致命的缺点,我们通过代码来说明:

public class Client {
	static Single1 single1;
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++){
			Runnable runnable = new Runnable(){
				public void run() {
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					single1 =Single1.getInstance();
					System.out.println(single1);
				}
			};
			new Thread(runnable).start();
		}
	}
}

代码含义:把懒汉式单例模式放到多线程下执行。
Thread.sleep(2000)是让当前执行单例模式的线程休眠2秒,让线程不那么快执行完,便于看到效果。
运行后的某一种结果:

com.design.single.Single1@34be8216
com.design.single.Single1@34be8216
com.design.single.Single1@34be8216
com.design.single.Single1@474e8d67
com.design.single.Single1@474e8d67
com.design.single.Single1@762589c3
com.design.single.Single1@474e8d67
com.design.single.Single1@474e8d67
com.design.single.Single1@34be8216
com.design.single.Single1@23194cf5

很明显,10个线程调用懒汉式单例模式后返回的对象居然有4个,这与单例模式的含义相违背,表明懒汉式单例模式在多线程下不可行,这是懒汉式单例模式致命的缺点。
分析执行过程可知,当线程1在执行if(single1 == null)后执行singe1 = new Single1()生成对象的同时,线程2也正好在判断if(single1 == null),由于线程1的对象还没有生成,所以线程2的判断为true,便进一步执行single1 = new Single1(),所以便会生成多个Single1对象。

为了解决这个问题,我们自然想到synchronized关键字,即在获取single1对象的方法getInstance()上加锁,使其成为同步方法:

public class Single1 {
	private static Single1 single1;   
	private Single1(){}         
	public static synchronized Single1 getInstance(){
		if (single1 == null){
			single1 = new Single1();
		}
		return single1;
	}
}

同步方法getInstance()能保证每次只有一个线程进入方法内部,这样便能保证单例。
同步方法的特点:通过synchronized关键字使得线程获得Single1类的内置锁从而保证每次只有一个线程执行getInstance()。进一步分析执行过程:线程1执行方法getInstance时获取Single1的内置锁,此时线程2过来想要执行方法getInstance,发现方法所属类的锁已经被线程1占有,所以线程2被阻塞,接着线程3,线程4…继续被阻塞,这样导致的结果就是执行的效率非常低。我们想想,其实只有线程1进入方法getInstance生成实例后,方法2、3、4…都不需要生成实例,因此也就没必要阻塞后面的线程,所以可以缩小同步的范围,即把从同步方法变成同步代码块。

双重检查的单例模式:

public class Single1 {
	private static volatile Single1 single1;   
	private Single1(){}         
	public static Single1 getInstance(){
		if (single1 == null){
			synchronized(Single1.class){
				if (single1 == null){
					single1 = new Single1();
				}
			}
		}
		return single1;
	}
}

双重检查的单例模式把同步从方法级别移到方法内部,只对必要的代码块进行同步;注意这里有两个判空,所以称为双重检查。第一次判空是所有线程都会执行的,当线程1判空后,就会获取Single1类的内置锁,线程1则势必会执行single1 = new Single1(),生成single1实例;假如线程1在执行同步代码块的时候,线程2进入方法getInstance,第一次判空为true,此时Single1的内置锁被线程1占有,因此被阻塞,当线程1执行完后退出同步代码块释放Single1的内置锁,线程2就会进入同步代码块,此时若线程3进来,因为线程1已经生成single1对象,所以线程3的第一次判空为false,则线程3执行返回对象,线程2进行第二次判空同样为false,直接返回single1实例。后面的线程在第一个判空处便为false。
双重检查单例模式的特点就是线程安全,延迟加载,效率高,比较常用。
注意:声明single1对象时,使用了volatile关键字。volatile关键字可以保证single1对象的可见性和有序性;这是防止指令重排从而保证single1对象的唯一。

静态内部类的单例模式

public class Single4 {
	private Single4(){}
	private static class Single4Inner{
		private static final Single4 SINGLE4 = new Single4();
	}
	public static Single4 getInstance(){
		return Single4Inner.SINGLE4;
	}
}

静态内部类的单例模式与饿汉式单例模式有点类似,唯一的不同在于,饿汉式单例模式中单例对象是随着Single2类加载而生成,而静态内部类单例模式则通过静态内部类产生单例对象,其利用静态内部类不会随着外部类的加载而加载的特性使得当getInstance方法被调用后Single4Inner类才会被加载,从而生成SINGLE4对象,同时用static和final修饰,保证只会生成一个SINGLE4对象,保证了其线程的安全。

枚举类单例模式:

public enum Single5 {
	SINGLE_5;
	public Single5 getInstance() {
		return SINGLE_5;
	}
}

枚举类单例模式是最实用的一种单例模式。枚举类本身带有私有的构造方法,而每个枚举对象都是static和final修饰的对象,表明对象只能被实例化一次,所以在枚举实例的时候就会产生单例。

以上是所有产生单例的方法,总结有:饿汉式单例,懒汉式单例,饿汉式静态模块单例,同步方法单例,双重检查单例,静态内部类单例以及枚举类单例

2 单例模式的原理

单例模式能够保证一个类仅有一个实例,并提供一个访问它的全局访问点。

3 单例模式的特点

故名思议,单例模式表明某个类只有一个实例。

4 单例模式的使用场景
5 参考资料

《大话设计模式》
枚举单例:https://www.cnblogs.com/ldq2016/p/6627542.html
https://www.cnblogs.com/zhaoyan001/p/6365064.html

6 源码

https://download.csdn.net/download/nobody_1/10887540

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值