单例模式
-
多线程的线程池的底层设计一般采用单例模式。
-
web应用的配置对象采用单例模式,因为配置文件是共享的资源。
-
网站的计数器,应用的计数器。
-
应用程序的日志应用一般采用单例模式,由于共享的日志文件一直处于打开状态,所以只能有一个实例去操作。
-
数据库的连接池的设计一般采用单例模式,数据库资源的打开或关闭非常消耗资源,所以用单线程维护。
单例模式的七种写法
第一种 懒汉式,线程不安全
定义一个指针,使用时用
getInstance
方法获取,线程不安全public class Singleton { private Singleton instance; private Single(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
第二种 懒汉式,线程安全
适合多线程情况,但是效率低
public class Singleton{ private static Singleton instance; private Single(){} public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
第三种 饿汉式
这种方式基于
classloader
机制避免了多线程的同步问题,没有lazy loading
public class Singleton{ private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance(){ return instance; } }
第四种 饿汉式2.0
类似第三种,在类初始化时就实例化
instance
public class Singleton{ static { instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return this.instance; } }
第五种 静态内部类
利用
classloader
的机制来保证初始化instance
时只有一个线程,跟第三种和第四种的细微差别是:第三种和第四种是只要Singleton
被装载了,那么instance
就会被实例化(未实现lazy loader
),而这种方式是Singleton类被装载了,instance
不一定被初始化,因为SingletonHolder
类没有被主动使用,只有显示通过调用getInstance
方式,才会显示装载SingletonHolder
类,从而实例化instance
。相对于第三四种的方式填补的缺点。- 实例化
instance
很消耗资源 - 不希望在
Singleton
类装载时就实例化
public class Singleton{ private static class SingletonHolder{ private static final Singleton INSTANCE = new Singleton(); } private Single(){} public static final Singleton getInstance(){ return SingletonHolder.INSTANCE; } }
第六种 枚举
此种方式是
Effective Java
作者Josh Bloch
提倡的方式,它可以避免多线程同步问题,并且能够防止反序列化重新创建新的对象,public enum Singleton { INSTANCE; public void whateverMethod(){ } }
第七种 双重校验锁
第二种的升级版,俗称双重检查锁定
public class Singleton{ private volatile static Singleton singleton; private Singleton(){} public static Singleton getSingleton(){ if(singleton == null){ synchronized(Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
问题
- 如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例,假定不是远端存取,例如一些
servlet
使用完全不同的类,装载器,这样会有两个servlet
访问单例类 - 如果
Singleton
实现了java.io.Serializable
的接口,那么这个类的实例就可能被序列化和复原,如果序列化一个,二复原多个,那么就会有多个单例对象
解决
1
private static Class getClass (String classname) throw ClassNotFoundException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null) classLoader = Singleton.class.getClassLoader(); return (classLoader.loadClass(className)); }
2
public class Singleton implements java.io.Serializable { public static Singleton INSTANCE = new Singleton(); protected Singleton(){} private Object readResolve(){ return INSTANCE; } }
总结
第四种和第三种类似,第一种不算,所以,一般五种写法,懒汉,饿汉,双重校验锁,枚举,静态内部类。
- 实例化