一、概念

  Java中单例模式是一种非常常见的设计模式,单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

二、特点

  1、单例类只有一个实例。

  2、单例类必须自己创建自己的唯一实例。

  3、单例类必须给所有其它对象提供这一实例。

三、种类

  1、懒汉式单例:懒汉式就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建。

public class Singleton {
	
     //定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取 private static Singleton instance = null; // 定义私有构造方法,表示只在类内部使用,亦指单例的实例只能在单例类内部创建(防止类外部通过 new Singleton()去实例化) private Singleton() { } //定义一个公共的公开的方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例 public static synchronized Singleton getInstance() { if(null == instance) { instance = new Singleton(); } return instance; } }

  线程A希望使用SingletonClass,调用getInstance()方法。因为是第一次调用,线程A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用SingletonClass,调用getInstance()方法,同样检测到instance是null(注意:这是在线程A检测完之后切换的),也就是说线程A并没有来得及创建对象——因此线程B开始创建。线程B创建完成后,切换到A继续执行,因为线程A已经检测过了,所以线程A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个SingletonClass的对象,因此单例对象创建失败,解决办法就是使用synchronized关键字加锁,保证线程安全。

  2、饿汉式单例:在加载类的时候就会创建类的单例,并保存在类中。这种方式比较常用,但容易产生垃圾对象,效率比较低,线程安全

public class Singleton {

	// 此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中
	private static final Singleton instance = new Singleton();
	
	// 定义一个私有的无参构造方法,防止该单例类被在外部实例化
	private Singleton() {
	
	}
	
	// 静态方法返回该类的实例
	public static Singleton getInstance() {
		return instance;
	}
}

  3、双重加锁机制单例:这种方式采用双锁机制,线程安全且在多线程下能保持高性能

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

  这里的双重指的的双重判断,而加锁单指那个synchronized,为什么要进行双重判断,其实很简单,第一重判断,如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例。  

  如果某个线程A发现instance还没初始化,那么就进入同步块初始化instance,如果在这期间有其他线程B进入,那么线程B就会等待进入同步块,等待A 线程退出同步块,instance已经被初始化了,线程B进入同步块后发现instance不为null,即退出同步块,不再进行初始化instance。 这样既实现了线程安全,又兼顾了效率,确实是很聪明的编码方式。但是问题来了,由于Java指令重排序的存在,会导致其他线程可能会得到未完全初始化的对象,所以JDK1.5版本之后扩展了volitile关键字,可以保证上述代码的正确性。

  volatile关键字的含义是:被其所修饰的变量的值不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存来实现,从而确保多个线程能正确的处理该变量。Volatile类型的变量不会被缓存在寄存器中(寄存器中的数据只有当前线程可以访问),或者其他对CPU不可见的地方,每次都需要从主存中读取对应的数据,这保证每次对变量的修改,其他线程也是可见的,而不是仅仅修改自己线程的局部变量

  4、静态内部类单例:使用类级内部类结合多线程默认同步锁,同时实现延迟加载和线程安全。

public class Singleton {
	
	private Singleton() {

        }
	
	private static class SingletonHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	
	public static final Singleton getInstance() {
		return SingletonHolder.INSTANCE;
	}

}

  所谓类级内部类,就是静态内部类,这种内部类与其外部类之间并没有从属关系,加载外部类的时候,并不会同时加载其静态内部类,只有在发生调用的时候才会进行加载,加载的时候就会创建单例实例并返回,有效实现了懒加载(延迟加载),至于同步问题,我们采用和饿汉式同样的静态初始化器的方式,借助JVM来实现线程安全。

四、使用

  在Spring中创建的Bean实例默认都是单例模式存在的。

 

posted on 2017-06-03 12:08  柠檬小镇  阅读(122)  评论(0编辑  收藏  举报