一、背景知识
在所有的主流23中设计模式中,单例模式作为最简单也最容易理解也是使用最普遍的设计模式,他是一种对象创建模式,用于产生一个对象的具体实例,他可以确保系统中一个类只产生一个实例。
单例模式的优点如下:
1、对于频繁创建的对象,可以省去new的操作锁花费的时间,对于一些重量级的对象创建来说可以说是一笔开销的节省;
2、由于new的次数变少了,系统内存的使用频率也会降低,这也减轻了GC的压力,缩短了GC停顿的时间,这是因为GC收集的时候去阻塞程序的执行。
单利模式的七种写法主要是:懒汉模式线程不安全、懒汉模式线程安全、饿汉模式、变种的饿汉模式、双重校验锁DCL、静态内部类、枚举类型。
下边主要分析一下:饿汉模式、懒汉模式线程安全、双重校验锁DCL、静态内部类四种,其他几种大致类似,不在赘述。
二、饿汉模式
/**
* 饿汉单例模式
* @author 小跳蛙
*
*/
public class Singleton {
//1、实例化对象必须是private,并且是static,是private是可以保证安全性
private static Singleton singleton = new Singleton();
//2、构造方法必须是私有化的,这样可以避免其他任意调用的情况
private Singleton() {
}
//3、提供方法以供外部获取实例对象,此方法必须为public static, public保证外部有权限能访问,
//static保证不需要先实例化Singleton对象就能访问此方法
public static Singleton getInstance() {
return singleton;
}
}
三、懒汉模式
/**
* 懒汉模式线程安全
* @author 刘威辰的秘密花园
*
*/
public class LazySingeton {
//声明
private static LazySingeton instance;
//私有化构造器
private LazySingeton() {
}
public synchronized LazySingeton getInstance() {
if(instance == null) {
instance = new LazySingeton();
}
return instance;
}
}
四、双重校验锁DCL
但是在多线程并发访问的情况下,每个线程每次获取实例都要判断下锁,效率比较低。为了提高效率,我加入了双重判断的方法,解决了效率的问题
public class SingleDemo {
private volatile static SingleDemo s = null;
private SingleDemo(){}
public static SingleDemo getInstance(){
/*如果第一个线程获取到了单例的实例对象,
* 后面的线程再获取实例的时候不需要进入同步代码块中了*/
if(s == null){
//同步代码块用的锁是单例的字节码文件对象,且只能用这个锁
synchronized(SingleDemo.class){
if(s == null){
s = new SingleDemo();
}
}
}
return s;
}
}
(相对的,就是把synchronized锁去掉,在这里就省略这段代码了)
五、静态内部类
/**
* 静态内部类
* @author 小跳蛙
*
*/
public class StaticSingleton {
private static class StaticSingletonHolder{
private static final StaticSingleton instance = new StaticSingleton();
}
private StaticSingleton() {
}
public static final StaticSingleton getInstance() {
return StaticSingletonHolder.instance;
}
}
简单分析一下:
1、可以实现延迟加载的功能,只有在调用getInstance()的方法才会创建单例对象,并且是通过类加载器机制保证只创建一个单例对象;
2、对于java类加载机制来说,当第一次访问类的静态字段的时候,会触发类加载,并且同一个类只加载一次。静态内部类也是如此,只会被加载一次,类加载过程由类加载器负责加锁,从而保证线程安全。
3、代码简单明了,值得信赖
六、总结:
第一种:饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。
第二种:就是懒汉模式线程安全,在单线程中一般可以保证线程安全,但是在多线程中,有可能会出现不同步,于是提出第三种。
第三种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗,但是这种方法代码量多,并且难以理解,还不能保证一些低版本JDK执行的正确性,所以也不推荐使用。
第四种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
大家也可以去http://mp.weixin.qq.com/s/3KATGzc-IIaP7lQOFJp6Ew看看,这是我对这篇文章及网上一些相关文章作出的总结,谢谢大家!!!