单例设计模式
在开发时,有些对象只需要一个,比如说:
程序中经常需要读取的某个配置文件、常用工具类、线程池、缓存、日志对象等等
这些对象,如果被创造出多个实例,就会导致许多问题,比如占用过多的资源,不一致的结果,等等。
所以说,单例模式的目的就是:
保证创建对象实例,有且只有一个。
1.将默认构造方法私有化,这样外界就无法直接创建类的实例
2.创建本类的唯一实例。
3.实例已经创建,那么外部怎么获取到这个唯一实例呢?将该实例静态化,但是为了控制访问,保证成员变量安全,将该实例私有化。
4.接着,提供一个public的获取该实例的方法,并且该方法静态化,否则无法直接获取。
那么饿汉式,为什么叫饿汉式呢?是因为,这种单例中,在类被加载时,就已经创建了该实例。
1.将默认构造方法私有化,这样外界就无法直接创建类的实例
2.声明类的唯一实例,使用private static修饰,注意哦,这里是声明,但是并没有创建实例
3.提供一个用于获取实例的方法,使用public static修饰。该方法中,先判断该类的唯一实例是否为空,也就是判断该实例有没有被创建过,如果被创建过,那么返回该实例,如果没有被创建过,那么创建该实例,也就是new Single();
package com.gome.util;
class Single1{ //私有化默认构造函数
private Single1(){ } //在本类中创建自己唯一的实例
private static Single1 instance = new Single1(); //写一个方法,供外界调用,得到这个唯一的实例
public static Single1 getInstance(){
return instance;
}
}
class Single2{
private Single2(){ }
private static Single2 instance;
public static Single2 getInstance(){
if(instance==null){
instance = new Single2();
}
return instance;
}
}
public class Test{
public static void main(String[] args){
Single1 s1 = Single1.getInstance();
Single1 s2 = Single1.getInstance();
if(s1==s2){
System.out.println("s1与s2是同一个实例");
}
Single2 s3 = Single2.getInstance();
Single2 s4 = Single2.getInstance();
if(s3==s4){
System.out.println("s3与s4是同一个实例");
}
}
}
饿汉式的特点是,加载类时比较慢,因为有创建实例的过程,但运行时获取对象的速度比较快。线程安全
懒汉式的特点是加载类时比较快,但是运行获取对象速度比较慢,线程不安全。
那么,问题来了?
如何才能使得其线程安全呢?首先我们想到的应该是,使用synchronized关键字,在getInstance方法上,比如:
public static synchronized Single getInstance(){}
但是这样的话,确实解决了线程安全的问题,但是却降低了性能,有很多不必要的同比,比如return操作,需要同步的只是创建实例的操作。
改进下的话,可以:
private volatile static Single instantce = null;
实例对象用volatile修饰,具有synchronized可见性,在每次使用变量时,会读取该变量的最新值。这样线程会自动发现该变量的最新值,这样一个线程实例化成功,其他线程就会立即发现。
综上所述,简单的单例模式,并不是完全可靠的,主要有两个因素在影响
1. 通过java的反射机制完全可以实例化构造方法为private的类,这样便使得java单例的实现失效
2. 考虑到线程的存在,在并发环境下,很可能会出现多个实例。
以上只是介绍单例的基本使用方式,其实单例还有很多种
比如:登记式单例
示例代码如下:
import java.util.Map;
//登记式单例模式
public class RegisterSingle{
/**
* 使用静态代码块,在类被加载的时候,就将类的实例注册进一个map中,
使用的时候,通过getInstance方法,传入类名 * 获取该类的实例
*/
//定义一个map用来存放类的实例
private static Map map = new HashMap();
static{
RegisterSingle single = new RegisterSingle();
map.put(RegisterSingle.class.getSimpleName(), single);
}
//私有化构造函数
private RegisterSingle(){
}
//写一个静态方法,供外部得到该类的唯一实例
public static RegisterSingle getInstance(String name){
if(name == null){
name = RegisterSingle.class.getSimpleName();
System.out.println("name == null===> name="+ name);
}
if(map.get(name) == null){
try {
map.put(name, (RegisterSingle) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
}
使用静态内部类的方式实现单例(理论上安全而且延迟加载)
另外一种完美实现的实现既线程安全又延迟加载的模式(Initialization on demand holder)使用静态内部类
Public class Singleton{ Private Singleton(){}; Public static class Singleton1{ Private static final Singleton instance = new Singleton();} Public static Singleton getInstance(){ Return Singleton1.instance;}}
这样就能保证在第一次调用getInstance()方法时,才会去初始化instance实例,而且该实例被定义为static,只会被初始化一次。
tips1
还有一个问题就是单例化类的序列化问题:如果单例类实现了serializable接口,这是要特别注意以为在默认情况下,每次反序列化时总会创建一个新的对象,注意系统就会出现多个对象了。解决方法:根据序列化可知:每次反序列化完成前都会去调用readResolve()方法,那就在该方法中,将用原对象取代新创建出来的对象。在是在该实现了序列化的类中再定义一个方法:
Public Singleton readResolve(){ sReturn instance; // instance是唯一的实例对象}