首先,单例模式使类在程序生命周期的任何时刻都只有一个实例,
然后,单例的构造函数是私有的,外部程序如果想要访问这个单例类的话,
必须通过 getInstance()来请求(注意是请求)得到这个单例类的实例。
1、饿汉式
package singleton;
/**
* 单例模式保证一个类仅有一个实例,同时这个类还必须提供一个访问该类的全局访问点。
*
* 饿汉式:new之后,便创建对象,以后不再改变,此种方式天生线程安全的
*/
public class SingletonDemo1 { //定义一个私有的静态全局变量来保存该类的唯一实例
private static SingletonDemo1 instance=new SingletonDemo1();
//构造函数必须是私有的,这样在外部便无法使用 new 来创建该类的实例
private SingletonDemo1(){
} //定义一个全局访问点 ,设置为静态方法 ,则在类的外部便无需实例化就可以调用该方法
public static SingletonDemo1 getInstance(){
return instance;
} }
2、懒汉式
package singleton;
/**
* 懒汉式:在需要的时候创建对象
* if()判断,保证线程的安全性
* @author Don
*
*/
public class SingletonDemo2 { private static SingletonDemo2 instance;
private SingletonDemo2(){
} //ͬ多线程访问时,当有一个线程访问时就加锁,防止产生多个实例
public static synchronized SingletonDemo2 getInstance(){
if(instance==null){
instance=new SingletonDemo2();
}
return instance;
}
}
3、静态内部类(结合了前两种的优势)
package singleton;
/**
* 静态内部类:兼备了高效调用和延迟加载的优势
* @author Don
*
*/
public class SingletonDemo3 { //只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。instance是static final类型
//保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性
private static class SingletonClassInstance{
private static final SingletonDemo3 instance=new SingletonDemo3();
}
private SingletonDemo3(){
} public static SingletonDemo3 getInstance(){
return SingletonClassInstance.instance;
} }
饿汉式和懒汉式区别:
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
至于1、2、3这三种实现又有些区别,
第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,
第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗
第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。