单例模式有好几种写法,作为使用相对最为频繁的模式来说,新手应该会经常碰见!
单例模式的主要特征以及关键点:
-
构造函数不对外开放,一般为private;
-
通过一个静态方法或者枚举返回单例类对象;
-
确定单例类有且只有一个,特别是在多线程环境下;
-
确保单例类对象在反序列化的时候不会重新构建对象;
单例类会暴露一个公有的静态方法,直接调用该方法进行实例化(唯一对象实例),获取这个单例对象的过程中要确保线程安全,在多线程的情况下尤为重点,必须保证多线程环境下构造的单例类的对象有且只有一个。
说白了就是!!!!保证实例对象有且只有一个!
基础的写法有
一.饿汉模式
public class Demo{
private static Demo mDemo;
private Demo(){};
public static synchroized Demo getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
eg:这个写法有个问题,因为使用了synchroized致使性能下降不少,即使第一次已经对该对象进行实例了,可是每次调用getInstance()的时候还是会进行同步,消耗不必要的资源。
总结:
优点:在使用的时候才进行实例,算是节约资源。
缺点:1 第一次加载的时候需要及时进行实例化,反映稍微有点慢。2.每次调用该方法都进行同步,造成不必要的资源开销。
所以,一般这个不考虑使用!
二. Double Check Lock (DCL)模式
public class Demo{
private static Demo mDemo = null;
private Demo(){
}
public static Demo getInstance(){
if(mDemo == null ){
synchronized(Demo.class){
if(mDemo == null ){
mDemo = new Demo();
}
}
}
return mDemo;
}
}
eg:这里有个特点,就是两次判断对象是否为空。第一层是为了不必要的同步,第二层是判断在空的情况下创建实例。
大家对于第二层的意思可能不清楚,看下面分析:
首先,mDemo = new Demo();这句代码不是一个原子操作,编译器会将其编译成多条汇编指令,大致一下三步骤:
-
给Demo的实例分配内存。
-
调用Demo()的构造函数,初始化成员字段。
-
将mDemo对象指向分配的内存空间。(这时候,mDemo就不是null了)
问题主要就是因为在java编译器允许处理器乱序执行,已经JDK1.5之前JMM(java Memory Model )中的Cahce,寄存器到主内存会回顺序的规定,上面的2 3 是顺序是没办法保证的,有可能是123 或者132顺序,如果是后者,那可能在多线程的情况下会出现一个很大的问题,设想一下,线程A执行到3然后还没有执行2 就切换到线程B,此时mDemo已经不是空的,但是它实际上并没有实例化构造函数已经成员字段等,相等于内存空间中还只是空的情况,所以当线程B取走mDemo的时候,再使用就出错了!这个就是DCL失效问题。不过在1.5后已经修改了对volatile的具体化,只要在 private static volatile Demo mDemo = null,则可以保证mDemo对象每次都在主内存中读取,就可以使用DCL的写法来完成单例模式。
总结:
优点:资源利用率高,第一次执行才实例化对象,效率高。
缺点:第一次加载慢,偶尔因为java内存模式会失效,在高并发条件下有一点的缺陷。虽然发生的概率很小。
这个模式是单例模式使用是最多的!(除了低于1.6JDK版本 或者 并发环境很高的 时候另外一说)
三 静态内部类单例模式
public class Demo{
private Demo(){ }
public static Demo getInstance(){
return DemoHolder.mDemo;
}
//静态内部类
private static class DemoHolder{
private state final Demo mDemo = new Demo();
}
}
eg:第一次加载Demo类的时候,并不会实例化它,只有在getInstance()的时候才会加载它,当第一次调用的时候会致使虚拟机加载DemoHolder类,这种方式不止可以确保线程安全,也可以确保单例对象的唯一性。同时也延迟了单例的实例化。
总结:优点:直接交由java虚拟机保证线程问题。并且在第一次调用后才实例节约资源。
缺点:加载需要时间,可能第一次也会偏慢点。
这个是最为推荐的单例模式实现方式!!!
以上是相对比较普遍的三种实现方法,相对而言。第三种方式是最为可取的,第二种除了两种特殊情况,也是可以选择的,第一种的缺点过于明显,在开发中最好做到不使用为妙。
四 当项目使用的单例模式比较多的时候,也可以使用一个容器将其存储起来,一是便于管理,二是可以对用户隐藏具体实现,降低一定的耦合性。
代码如下:
public class SingletonManager{
private static Map<String,Object>objectMap = new HashMap<String,Object>;
private SingletonManager(){};
public static void registerService(String key, Object instance){
if(!objectMap.containsKey(key)){
objectMap.put(key,instance);
}}
public static Object getService(String key){
return objectMap.get(key);
}
}
以上文章是看书籍总结的而来,原创作品,转载请注明出处!
网友评论