一、单例模式的定义
单例模式:确保只有一个类只有一个实例,并提供一个全局访问点。
应用场景:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象、充当打印机、显卡等设备的的驱动程序的对象。在这些对象中,只能有一个实例。
根据此单一实例产生的时机不同,可以将其划分为懒汉式、饿汉式。
二、三种方式介绍
(1)懒汉式:
package SingleTon;
/**
* Created by L_kanglin on 2017/3/7.
* 懒汉式,经典的写法
*/
public class SingleTon {
//静态实例变量
private static SingleTon instance;
//私有化构造函数
private SingleTon(){
}
//静态public方法,向整个应用提供单例获取方式
public static SingleTon getInstance(){
if(instance==null){
instance= new SingleTon();
}
return instance;
}
}
其特点是延迟实例化,上述代码中instance是一个静态变量,如果instance是空的,表示还没有创建实例,而如果它不存在时,我们就利用私有的构造器产生一个Singleton实例,并把它赋值给instance静态变量中,此时我们发现,如果我们不需要它,它就永远不会产生,这也就是延迟实例化。
而如果instance不为空,说明之前已经创建这个对象,我们直接跳到return语句,并将其返回。
缺点:当处于多线程环境中时,当两个线程同时需要执行这段代码时
SingleTon sing = SingleTon.getInstance();
getInstance()方法是静态的,意味着它是一个类方法,所以可以在代码的任何地方使用SingleTon.getInstance()访问它。这和访问全局变量一样,然而当两个线程同时执行上述代码时,就会出现错误。
改进后的懒汉式的线程安全写法:
a、将getInstance()变成同步(synchronized)方法,多线程中的安全问题基本解决。
package SingleTon;
/**
* Created by L_kanglin on 2017/3/7.
* 懒汉式,改进一,加同步
*/
public class SingleTon {
//静态实例变量
private static SingleTon instance;
//私有化构造函数
private SingleTon(){
}
//静态public方法,向整个应用提供单例获取方式
public static synchronized SingleTon getInstance(){
if(instance==null){
instance= new SingleTon();
}
return instance;
}
}
通过增加synchronized关键字到getInstance()方法中,我们使每个线程在进入这个方法之前,要先等候别的线程离开该方法。也就是说,不会有两个线程可以同时进入这个方法。
但同步的同时,也带来了性能的降低,只有第一次执行这个方法时,才真正的需要同步,一旦设置好instance变量后,就不再需要同步这个方法了,之后的调用,都是一种性能的消耗。
b、使用双重检查锁,首先检查是否实例创建了,如果尚未创建,才进行“同步”,这样一来,只有第一次会同步。
package SingleTon;
/**
* Created by L_kanglin on 2017/3/7.
* 懒汉式,改进二,双重检查加锁
*/
public class SingleTon {
//静态实例变量
private volatile static SingleTon instance;
//私有化构造函数
private SingleTon(){
}
//静态public方法,向整个应用提供单例获取方式
public static SingleTon getInstance(){
if(instance==null){
synchronized(SingleTon.class){
if(instance==null){
instance= new Singleton();
}
}
}
return instance;
}
}
首先检查实例,如果不存在,就进入了同步区块,注意只有第一次执行这个部分,进入区块后,再检查一次,如果仍是null,才创建实例。
volatile关键字:当instance变量被初始化成SingleTon实例时,多个线程正确的处理instance变量。
(2)饿汉式:应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太重,比较急切的创建此单件。
public class SingleTon {
//在静态初始化器中创建单例,这段代码保证了线程安全
private static SingleTon instance = new SingleTon();
//私有化构造函数
private SingleTon(){
}
//直接返回已创建好的实例
public static SingleTon getInstance(){
return instance;
}
}
依赖JVM在加载这个类时马上创建此唯一的单件实例。JVM保证在任何线程访问instance静态变量之前,一定先创建此实例。
三、分析
懒汉式比较懒,只有当调用的时候,才会去初始化这个单例;饿汉式就是这个类一旦被加载,就创建了单例,在getInstance时,此时已经存在了这个单例。
(1)线程安全
饿汉式是线程安全的,可直接用于多线程而不会出现问题。
懒汉始经典方式是线程不安全的,但通过同步和双重检查加锁的机制,可以实现线程安全。
(2)资源的占用
懒汉式也就是延迟实例化,在第一次使用该对象时,才会实例化出来,如果要做的事情比较多,性能上会有些延迟。
饿汉式在创建的时候就实例化出来,会首先占用一定的内存,并且第一次调用它的时候,速度会很快,由于初始化的过程早已完成。
补充:插入某个评论
假设没有关键字volatile的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行instance = new Instance(),该构造方法是一个非原子操作,编译后生成多条字节码指令,由于JAVA的指令重排序,可能会先执行instance的赋值操作,该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后instance便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程B进入,就会看到一个不为空的但是不完整(没有完成初始化)的Instance对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。
memory=allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance=memory; //3:设置instance指向刚分配的内存地址
上面3行代码中的2和3之间,可能会被重排序导致先3后2,
文章只是作为自己的学习笔记,借鉴了网上的许多案例,如果觉得阔以的话,希望多交流,在此谢过…