基本面试时80%都会问到“你知道哪些设计模式”
一般我们回答 “经常用到的设计模式有单例模式,工厂模式等”,
设计模式有23种,既然你回答了单例模式,工厂模式,那接下来他又会问 “你项目中有用到单例模式吗?”
你说“有"
接下来他又会问”你项目中哪些用到了单例模式?为什么用?“
你说”项目中没有用到单例“
他又会问“那你了解单例模式吗,什么情况下用单例模式?“
你说”了解一点,不深,“
有的面试官会拿出纸笔让你写一段单例模式的代码出来。。。。。当然这是个别案例 ,我就遇到了。。。。
当你写完面试官又会问”单例也分懒汉,饿汉,你知道吗”
你说“知道”
“那这懒汉和饿汉又有什么区别?什么时候用懒汉什么时候用饿汉?
完了,这个话题有的聊。。。。。。。
因为经常遇到,今天来把这些整明白了,摔倒过的地方不能再摔倒。。。
所谓单例 ,指的就是单实例,有且仅有一个类的实例。
那我们什么时候用单例呢,
例如,有一个类statistical,这个类里有一个属性 number ,当有用户访问我们的网站时,我们就让number+1 ,这样能达到网站统计访问量的效果,因为我们知道要访问类的属性,需要先实例化这个类,但是每次new statistical() 都会是一个新的对象,为了让统计的访问量准确,这里statistical类就需要做成单例。
总结一句话,当某个资源共享时,这个资源需在的类需要单例。
单例模式有两种创建方式:懒汉式和饿汉式
不论是创建懒汉还是创建饿汉都有几个要点
1.私有构造方法。
2.私有静态引用指向自己实例
3.以自己实例为返回值的公有静态方法
饿汉式:意思饿了,饥不则食,只要有吃的必急着吃,在这里的意思就是:在加载类的时候就会创建类的实例,并保存在类中。
饿汉式代码片段:
//饿汉式
public class Test_eh {
//此处定义私有类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中
private static Test_eh instance = new Test_eh();
//定义私有无参构造器,用于单例实例
private Test_eh(){
}
//定义公开方法,返回已创建的单例
public static Test_eh getInstance(){
return instance;
}
}
饿汉式优点
1.线程安全
2.在类加载的同时已经创建好一个静态对象,调用时反应速度快
饿汉式缺点
资源效率不高,可能getInstance()永远不会执行到,但执行该类的其他静态方法或者加载了该类(class.forName),那么 这个实例仍然初始化
懒汉式:看字面意思就是一个字,懒,非得叫你才去干,在这里的意思就是,不在系统加载这个类时创建,而是在第一次使用的时候再创建。
懒汉式代码片段:
//懒汉式
public class Test_lh {
//定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取
private static Test_lh instance = null;
//定义私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建
private Test_lh(){
}
//定义一个公共的公开的方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例,
public static synchronized Test_lh getInstance(){
//多个线程判断instance都为null时,在执行new操作时多线程会出现重复情况,所以为了线程安全,
//使用synchronized关键字来确保只会生成单例
if(instance == null){
instance = new Test_lh();
}
return instance;
}
}
懒汉式优点:类的实例在第一次被使用时构建,延迟初始化。避免了饿汉式的那种在没有用到的情况下创建实例,资源利用率 高,不执行getInstance()就不会被实例,可以执行该类的其他静态方法。
懒汉式缺点: 懒汉式在单个线程中没有问题,但多个线程同事访问的时候就可能同事创建多个实例,解决这个问题的办法就是 加锁synchonized,第一次加载时不够快,多线程使用不必要的同步开销大。
懒汉式实现单例模式中,有使用synchronized关键字来同步获取实例,保证单例的唯一性,但是上面的代码在每一次执行时都要进行同步和判断,会拖慢速度,使用双重加锁机制正好可以解决这个问题:
双重加锁机制片段:
//双重加锁式
public class Test_lh_2 {
//定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,
//而要使用提供的公共方法来获取
private static volatile Test_lh_2 instance = null;
//定义私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建
private Test_lh_2(){
}
//定义一个公共的公开的方法来返回该类的实例
public static Test_lh_2 getInstance(){
//第一重判断,如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例
if(instance == null){
/*
* 如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个
* 调用同时进行时,导致生成多个实例, 有了同步块,每次只能有一个线程调用能访
* 问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,
* 之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例
*/
synchronized (Test_lh_2.class) {
if(instance == null){
instance = new Test_lh_2();
}
}
}
return instance;
}
}
使用了双重加锁机制后,程序的执行速度 有了显著提升,不必每次都同步加锁。
饿汉会占用较多的空间资源,因为在其类加载时就会完成实例化,而懒汉式又存在执行速率慢的情况,双重加锁机制又有执行效率差的毛病,网上还有一种完美的方式可以规避这些毛病,理解的不深,不做过多解释。
静态内部类代码片段:
//静态内部类方式
public class Test {
/*
* 创建静态内部类,这种内部类与其外部类之间并没有从属关系,加载外部类的时候,并不会同时加载
* 其静态内部类,只有在发生调用的时候才会进行加载,加载的时候就会创建单例实例并返回,有效
* 实现了懒加载(延迟加载),至于同步问题, 我们采用和饿汉式同样的静态初始化器的方式,借
* 助JVM来实现线程安全
*/
public static class TestNBN{
private static Test instance = new Test();
}
//定义无参构造器,用于单例实例
private Test(){
}
//义公开方法,返回已创建的单例
public static Test getInstance(){
return TestNBN.instance;
}
}
总结:
在资源开销不大的情况下,一般采用饿汉式,若对资源十分在意可以采用静态内部类方式,不建议采用懒汉式及双重检测式。