定义:
单例模式,是一种常用的软件设计模式,在他的核心结构中只包含一个被称为单例的特殊类。目的是保证系统中只有一个
实例。也就是一个类只有一个对象。
特点:
1、单例类只有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式的特点:
1、私有的构造方法。
2、指向自己实例的私有静态引用。
3、以自己实例为返回值的静态的共有方法。
单例模式根据实例化对象时机的不同分为两种:
一种是饿汉式单例,一种是懒汉式单例。
饿汉式单例在单例类被加载时候,就实例化了一个对象交给自己的引用;
懒汉式在调用取得实例方法的时候才会实例化对象。
第一种:线程不安全(懒汉式)
public class Singleton{
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
这种是懒加载,线程不安全,不推荐。
第二种:线程安全(懒汉式)
public class Singleton{
private static Singleton singleton;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
线程安全,但是效率极其低下,同步锁锁的是对象,每次取对象都有枷锁,因此不推荐。性能很低。
第三种,线程安全(饿汉式):
public class Singleton {
private static Singleton instance = new Singleton3();
private Singleton(){}
public static synchronized Singleton getInstance(){
return instance;
}
}
这种方式基于classloder机制避免了多线程的同步问题,但是instance在类加载的时候就实例化,这时候初始化instance
显然没有达到懒加载的效果。不推荐!
第四种,线程安全(饿汉模式):
public class Singleton{
private static Singleton instance;
static{
instance=new Singleton;
}
private Singelton(){}
public static synchronized Singleton gentInstance(){
return instance;
}
}
这种等同于第三种
第五种,线程安全(静态内部类)(常用)
public class Singleton{
private Singleton(){}
public static synchornized Singleton getInstance(){
return SingletonHolder.instance;
}
private static class SingeletonHolder{
private static Singleton instance=new Singleton();
}
}
这种方式同样使用了classloder的机制来保证初始话instance时,只有一个线程,他跟第三种第四重方式不同的是:
第三种和第四种方式只要Singleton类别加载了,娜美instance就会被实例化(没有达到懒加载的效果),而这种方式是Singleton类被加载了,instance不一定别初始化,因为SingletonHolder类没有被主动使用,只有显示通过调动getInstance方法时,才会显示装载SingleHolder类,从而实例化instance。如果实例化instance很消耗资源,我想让他延迟加载,另一方面,我不希望在SingleHolder类加载时候就实例化,因为我不能确定SingleHolder类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的,这个时候,相比第三种第四章方式就显得很合理。
第六种(枚举):
public enum Singleton{
INSTANCE;
public void whateverMethod(){
}
}
这种方式是Effective Java作者Josh Bloch 提倡的方式,他不仅能避免多线程同步问题,二期还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,这种方式不免让人 感觉陌生,在实际工作中,很少看到有人这么用。
单例模式还有一种比较常见的形式:双重锁的形式
public class Singleton{
private static volatile Singleton intance=null;
private Singleton(){}
public static Singleton getInstance(){
if(instace=null){
sychronized(SingletonClass.Class){
if(instance=null){
instance=new Singleton();
}
}
}
return instance;
}
}
这种方式:先检查实例是否存在,如果不存在才进入下面的同步代码块,在同步代码块中,线程安全的创建实例,
再次检查实例是否存在,如果不存在才真正的创建实例。
双重检查加锁:
* “双重检查加锁“的方式可以既实现线程安全,又能够使性能不受到很大的影响。
* 那么什么是”双重检查加锁“机制呢?
* 所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,
* 而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,
* 这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的
* 情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了
* 多次在同步情况下进行判断所浪费的时间。
* 双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile
* 修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而
* 确保多个线程能正确的处理该变量。
*
* 说明:由于volatile关键字可能会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率并不是
* 很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用”双重检查加锁“
* 机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
总结:
这里需要两问题注意:
1,如果单例有不同的类加载器加载,那便有可能存在多个单例类的实例。假定不是远端存取,假如一些servlet容器对每隔servlet使用都完全不同的类加载器,这样的话,如果有两个servlet访问同一个单例类,他们就都会有各自的实例。
解决办法:
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
2,如果Singleton实现了java.io.Serializable接口,那么这个累的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
解决办法:
public class Singleton implement java.io.Serializable{
public static Singleton InSTANCE= new Singleton();
protect Singleton(){}
private Object readResovle(){
return INSTANCE;
}
}
单例模式的优点:
1,在内存中只有一个对象,节省内存空间。
2,避免频繁的创建销毁对象,可以提高性能。
3,避免对共享资源的多重占用。
4,可以全局访问。
单例模式的缺点:
1,扩展困难,由于getInstance静态函数没办法生成子类的实例。如果要拓展,只有重写那个类。
2,隐式使用引起类结构不清晰。
3,导致程序内存泄露的问题。