Java中的单例模式

来源:互联网 发布:注册域名后怎么建站 编辑:程序博客网 时间:2024/06/07 23:29

转载出处:

http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html

http://www.cnblogs.com/hupp/p/4487521.html


第一种(懒汉,线程不安全,只适合单线程环境):

package test;public class Singleton {    private static Singleton instance=null;    private Singleton(){            }    public static Singleton getInstance(){        if(instance==null){            instance=new Singleton();        }        return instance;    }}
注解:

Singleton的静态属性instance中,只有instance为null的时候才创建一个实例,构造函数私有,确保每次都只创建一个,避免重复创建。
缺点:只在单线程的情况下正常运行,在多线程的情况下,就会出问题。例如:当两个线程同时运行到判断instance是否为空的if语句,并且instance确实没有创建好时,那么两个线程都会创建一个实例。

第二种(懒汉,线程安全):

public class Singleton {    private static Singleton instance=null;    private Singleton(){            }    public static synchronized Singleton getInstance(){        if(instance==null){            instance=new Singleton();        }        return instance;    }}
注解:

在第一种的基础上加上了同步锁,使得在多线程的情况下可以用。例如:当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。第一个线程发现实例没有创建,创建之。第一个线程释放同步锁,第二个线程才可以加上同步锁,执行下面的代码。由于第一个线程已经创建了实例,所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例。
缺点:每次通过getInstance方法得到singleton实例的时候都有一个试图去获取同步锁的过程。而众所周知,加锁是很耗时的,效率很低,能避免则避免。

第三种(饿汉,建议使用):

public class Singleton {    private static Singleton instance=new Singleton();    private Singleton(){            }    public static Singleton getInstance(){        return instance;    }}

注解:

初试化静态的instance创建一次。如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。而降低内存的使用率。

缺点:没有lazy loading的效果,从而降低内存的使用率。

第四种(静态内部类,建议使用):

public class Singleton {    private Singleton(){            }    private static class SingletonHolder{        private final static Singleton instance=new Singleton();    }    public static Singleton getInstance(){        return SingletonHolder.instance;    }}
注解:

定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。
优点:达到了lazy loading的效果,即按需创建实例。

第五种(双重校验锁):

public class Singleton {    private volatile static Singleton instance=null;    private Singleton(){            }    public static Singleton getInstance(){        if(instance==null){            synchronized(Singleton.class){                if(instance==null){                    instance=new Singleton();                }            }        }        return instance;    }}
注解:只有当instance为null时,需要获取同步锁,创建一次实例。当实例被创建,则无需试图加锁。
缺点:用双重if判断,复杂,容易出错。

第六种(枚举):

public enum Singleton {       INSTANCE;       public void whateverMethod() {       }  } 
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。

总结

有两个问题需要注意:

     1、如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类 装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。

     2、如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。


对我来说,我比较喜欢第三种和第四种方式,简单易懂,而且在JVM层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现lazy loading效果时才会使用第四种方式,另外,如果涉及到反序列化创建对象时我会试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第五种方式,毕竟,JDK1.5已经没有双重检查锁定的问题了。


我们系统在Web容器启动的时候,加载初始化Servlet的时候,会实例化一下对象,这样在后面使用的时候可以判断当前实例是否为空,不为空,返回实例。





0 0