以下内容仅作为我个人的学习参考进行记录,无意冒犯,如有不当之处,还请指正!
单例,从字面意思看, 就是单独的实例, 表示这个实例是唯一的!官方定义是:确保一个类只有一个实例, 并提供一个全局访问点。
那么问题来了,为什么需要这种只有一个实例的类呢?答:1、在内存中只有一个对象,节省内存空间;2、避免频繁的创建销毁对象,可以提高性能;3、避免对共享资源的多重占用;4、可以全局访问。如此优异的条件造就了单例模式NO.1的使用率。
适用于的场景有:
1、需要频繁实例化然后销毁的对象;
2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
3、有状态的工具类对象;
4、频繁访问数据库或文件的对象;
5、以及其他我没用过的所有要求只有一个对象的场景。
使用单例模式的时候还需要注意以下几点:
1、只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象;2、不要做断开单例类对象与类中静态引用的危险操作;
3、多线程使用单例使用共享资源时,注意线程安全问题。
PS: jvm垃圾收集器是不会回收单例对象的。
例子:
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
定义一个静态变量, 当变量存在就返回,不存在就直接创建一个,这样就只有一个实例了。
如果一般的情况,这段代码好像没有什么问题,能确保只有一个实例。
但是, 这时候又要扯到另一个面试必问的问题了, 多线程问题(当有多个线程去执行获取实例的方法时, 可能会同时会判断到uniqueInstance == null 这个条件,这时候就会同时创建实例,这个类就有两个实例了。)
我们遇到同步问题的时候,大多数开发人员脑海中立刻回浮现出一个单词 : synchronized。确实如此, 只要用synchronized对方法进行修饰, 多线程的灾难几乎就可以轻易的解决。
public class Singleton {
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton = new Singleton();
}
return singleton;
}
}
这个方法好像是操作起来最快的方法了, 加一个关键字进行修饰就可以解决。
但是,在很多书上都会提及, synchronized关键字, 是一个十分重量级的东西,会很大的影响性能,无论是修饰方法或者同步一个代码块。
所以,如果你的应用程序能够接受这个造成的额外的性能负担, 那么上述代码就是你想要的单例模式,即懒加载或者懒汉模式。
与之相对应的当然还有饿汉模式:
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
这种方式会在程序编译后立即加载这个类并创建唯一的实例,而不是在运行时创建, 这样就可以保证只有一个实例。
但是这个类一直都没有被用到的话,这就会浪费空间了,这也是这个方式的一个缺点。
public class Singleton {
private volatile static Singleton singleton; //声明成 volatile
private Singleton (){}
public static Singleton getSingleton() {
if(singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查singleton == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。
这里我们需要注意的是volatile关键字,为什么要用这个关键字呢, 因为虽然synchronized能保证进入创建实例的代码是同步的,但是new Singleton() 这个操作缺却不是原子的。
在创建实例的时候,我们的JVM一般会做三件事:
- 给uniqueInstance分配内存;
2.调用类的构造方法初始化成员变量;
3.将uniqueInstance对象指向分配的内存空间。
当操作3完成之后,其实这个对象就已经不为null了。
而JVM中的JIT(即时编译器)存在指令重排序的优化,所以,步骤2和步骤3的顺序是不能保证的。
最终可能为1-2-3, 也可能为1-3-2,而如果是后者的话,先完成了内存的分配,而这时候另一个线程进来了,判断实例不为null, 直接返回,
但是这个实例却没有经过第2步初始化, 程序就理所当然的报错了。
volatile这个关键字能保证修饰的参数对所有的线程都是可见的, 并且禁止指令重排序优化。
对所有的线程可见,是表示线程中不会存在该实例的副本,每次需要拿的话都会去主内存中拿。
而禁止指令重排序优化,就如上述所说,能保证他的操作的执行顺序在JIT进行优化时不会被打乱。
一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举,这里只简单介绍一下前三种,因为实际开发中用的比较少,大部分都是第三方开发的时候用的比较多,这里我就不详细写了。
有想要详细了解的可以看一下http://blog.csdn.net/nsw911439370/article/details/50456231这位大神写的也是比较好的
网友评论