单例模式的
本质:
控制实例数目
定义:
保证一个类仅有一个实例,并提供一个它的全局访问点。
单例模式有两种实现方式:饿汉模式和懒汉模式。
懒汉式实现代码:
public class Singleton{
private static Singleton uniqueInstance = null;
private Singleton(){}
private static synchronized Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
饿汉式实现代码:
public class Singleton{
private static Singleton uniqueInstance = new Singleton();
private Singleton(){}
private static synchronized Singleton getInstance(){
return uniqueInstance;
}
}
单例模式实现:
1.私有化构造函数;
2.提供获取实例的方法;
3.把获取实例的方法变为静态;
4.定义存储实例的属性;
5.把这个属性也定义为静态的;
6.实现控制实例的实现(懒汉式);
饿汉式实现方式体现了延迟加载的思想(简单说就是等到用这个资源的时候才加载,节约了资源);同时还体现了缓存的思想,当一个资源被频繁利用的时候,如果每次操作都从数据库或者硬盘上获取会很慢,所有有一种方式是存到内存中,要用到的时候,现在内存里面找,有就直接使用,从而节省大量时间。缓存是一种典型的利用空间换时间的方案。
时间和空间
比较两种实现方式:懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,就一直不被创建,则节约内存空间。
饿汉式是典型的空间换时间,当类装载时就会被创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节约了运行时间。
线程安全
从线程安全的角度来看,不加同步的懒汉式单例模式是线程不安全的,比如,有两个线程,一个A线程,一个B线程,它们同时调用getInstance()方法,那就可能导致并发问题,实例如下:
饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。
实现懒汉式的线程安全
1.加synchronized,降低了访问效率,而且每次都还需要判断。
2.“双重检测加锁”的方式实现,既使线程安全,又使性能不受太大影响,实现:
使用到volatile关键字:被volatile修饰的变量的值,将不被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
其实使用volatile和不使用都是线程安全的(主要是synchronized代码块保证线程安全),主要作用不是在线程安全,而是禁止在多线程的情况下代码重排序(new Singleton()时可能会出现重排序)
根据上图步骤解析(个人理解):例如有两个线程,A线程运行了伪代码的1,3步骤还没有运行到2,此时B线程进入方法2步骤,它会对变量进行取值操作,如果没有volatile的话它会直接执行取值,此时值为null,B线程就会往下执行,到synchronized代码块进行排队;如果有volatile,变量就不会重排序,取值操作会在执行完伪代码2步骤后执行,此时取值操作就会取到对象,线程直接会输出对象。还有一种理解是, CPU缓存中的volatile字段被一个线程修改后,其他CPU缓存中的线程在读 本地CPU缓存的 volatile字段时,就必须读取更新过的字段,所以B线程操作变量必须等A线程更新完才进行。
(频繁更改、改变或写入volatile字段 有可能导致性能问题)
实现代码:
public class Singleton{
private volatile static Singleton uniqueInstance = null;
private Singleton(){}
public static Singleton getInstance(){
if(uniqueInstance == null){
synchronized(Singleton.class){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
更好单例实现模式
解决方案(Lazy initialization holder class模式)
采用静态初始化器的方式,它可以由JVM来保证线程的安全性。
实现代码:
public class Singleton{
private Singleton(){}
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
静态内部类和它外部类没有直接联系,只有在第一次调用getInstance()方法,第一次读取SingletonHolder.instance时,SingletonHolder类得到初始化,而这个类在装载并被初始化的时候才会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,所有只会在虚拟机装载类的时候初始化一次,并由虚拟机保证它的线程安全性。
优势:getInstance方法并没有被同步,并只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
用枚举来实现单例(最佳方法)
用枚举实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。
实现代码:
public enum Singleton{
/**定义一个枚举的元素,他就代表了Singleton的一个实例**/
uniqueInstance;
/**单例也可以有自己的操作方法**/
public void singletonOperation(){
//功能处理
}
}
使用枚举实现单实例控制会更简洁,而且无偿的提供了序列化机制,并由JVM从根本上提供了保障,绝对防止多次实例化,是更简洁,高效,安全的实现方式。