单例模式确保一个类只有一个实例,并提供一个全局访问点。
某些对象我们只需要一个,比如线程池、缓存、注册表等等。如果这些类拥有多个实例,可能会产生很多问题。
使用单例模式可以确保我们使用的这些全局资源只有一份。
一个经典的单例模式的实现:
publicclassSingleton{
privatestaticSingleton uniqueInstance;
privateSingleton(){}
publicstaticSingleton getInstance(){
if(uniqueInstance==null){
uniqueInstance=newSingleton();
}
returnuniqueInstance;
}
}由于Singleton类没有公共的构造方法,我们并不能直接创建这个类的实例,而是只能通过调用静态的getInstance()方法来获取对单例对象的一个引用。当调用getInstance()时,如果该类还没有任何实例则创建一个实例并返回对它的引用,如果已存在一个实例,则直接返回该实例的一个引用。
这样就确保了单例的类最多只能有一个实例。
多线程下的隐患
在多线程的情况下,如果两个线程几乎同时调用getInstance()方法会发生什么呢?有可能会创建出两个该类的实例。
我们可以将getInstance()方法变为同步方法来解决这个问题:
publicclassSingleton{
privatestaticSingleton uniqueInstance;
privateSingleton(){}
publicstaticsynchronizedSingleton getInstance(){
if(uniqueInstance==null){
uniqueInstance=newSingleton();
}
returnuniqueInstance;
}
}
性能问题
然而,事实上,我们只有在uniqueInstance为null的时候才需要进行同步,当这个类已经有实例之后就不存在多线程隐患了。
因此我们将getInstance()方法变为同步方法有可能很大程度的拖垮性能。
如果将getInstance()方法变为同步方法真的影响到了性能,我们可以选择在静态初始化时创建这个单例。
publicclassSingleton{
privatestaticSingleton uniqueInstance=newSingleton();
privateSingleton(){}
publicstaticSingleton getInstance(){
returnuniqueInstance;
}
}
这样自然也能确保单例。
问题是,前面的例子中都是在需要一个实例的时候在创建单例,这个例子中在类初始化时就创建了单例。如果这个对象非常耗资源,而程序中又一直没有用到它,这样便是在浪费资源了。
“双重检查加锁”
publicclassSingleton{
privatevolatilestaticSingleton uniqueInstance;//volatile修饰被不同线程访问和修改的变量
privateSingleton(){}
publicstaticSingleton getInstance(){
if(uniqueInstance==null){
synchronized(Singleton.class){//对整个类加锁
if(uniqueInstance==null){
uniqueInstance=newSingleton();
}
}
}
returnuniqueInstance;
}
}这样就可以在节省资源的同时确保正确性和高效性。只是实现方法有点不够简洁了。