设计模式是老生常谈的问题,有人工作多年却对设计模式一窍不通,但是更多的人是懂一点点,但是不求甚解。其实这样不好,暂且不说在工作中的应用,即便是在面试时,被面试官问到设计模式时一脸懵逼,是非常尴尬的事情。本文不废话,不谈大篇理论教学,只针对面试,给出设计模式的关键点,从应试的角度,让大家认识和理解设计模式。
首先搞清楚一点,设计模式不是高深技术,不是奇淫技巧。设计模式只是一种设计思想,针对不同的业务场景,用不同的方式去设计代码结构,其最最本质的目的是为了解耦,延伸一点的话,还有为了可扩展性和健壮性,但是这都是建立在解耦的基础之上。
我们都知道大名鼎鼎的GoF的21种设计模式,看过head first的这本书的人应该不少。针对这21种设计模式,面试官问出的问题可能千变万化,让人莫名的忧(dan)伤(teng),但是不要怕,只要你搞清楚了每种设计模式的关键点和精髓,就可以举一反三,迎刃而解了。
今天我们来看看最简单的单例模式。理论我就不介绍了,没听说过的可以去查一下。即便是最简单的单例,也有关键点。举个不太恰当地例子,看过修仙修道之类小说的同学都知道,一般阵法高手做阵都有一个或多个“阵眼”,同理,我们每种设计模式也有“阵眼”,那么单例的“阵眼”在哪里?各位莫慌,我们先来看看代码。根据单例模式的理论:保证系统中只有一个实例,于是我撸了以下代码
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
Singleton
{
private
Singleton
(
)
{
}
//关键点0:构造函数是私有的
private
static
Singleton
single
=
null
;
//关键点1:声明单例对象是静态的
public
static
Singleton
GetInstance
(
)
//通过静态方法来构造对象
{
if
(
single
==
null
)
{
//关键点2:判断单例对象是否已经被构造
single
=
new
Singleton
(
)
;
}
return
single
;
}
}
|
好了,如果我是面试官,你是候选人,我要你撸个单例给我,你撸以上代码问我资词不资词,我肯定是不资词的。为什么?真的不是因为我想搞个大新闻,而是这段单例的代码一般情况下还是可以勉强运行,但是,遇到多线程的并发条件下就大清药丸了。因为这段代码是线程不安全的,有可能给new出多个单例实例,都多个了,还是屁的“单例”啊。
好,废话不多说,继续撸代码,你可能会说:”不是说线程不安全吗?小爷我加上线程安全判断呗,度娘在手天下我有,来了您呐~~~“
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
Singleton
{
private
Singleton
(
)
{
}
//关键点0:构造函数是私有的
private
static
Singleton
single
=
null
;
//关键点1:声明单例对象是静态的
private
static
object
obj
=
new
object
(
)
;
public
static
Singleton
GetInstance
(
)
//通过静态方法来构造对象
{
if
(
single
==
null
)
//关键点2:判断单例对象是否已经被构造
{
lock
(
obj
)
//关键点3:加线程锁
{
single
=
new
Singleton
(
)
;
}
}
return
single
;
}
}
|
好了,这回该消停了吧,锁也加了,线程也安全了。But,你还是太连清。。。这样依然有问题。问题在哪里?就在关键点2,检测单例是否被构造。虽然这里判断了一次,但是由于某些情况下,可能有延迟加载或者缓存的原因,只有关键点2这一次判断,仍然不能保证系统是否只创建了一个单例,也可能出现多个实例的情况,那么怎么办呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
class
Singleton
{
private
Singleton
(
)
{
}
//关键点0:构造函数是私有的
private
static
Singleton
single
=
null
;
//关键点1:声明单例对象是静态的
private
static
object
obj
=
new
object
(
)
;
public
static
Singleton
GetInstance
(
)
//通过静态方法来构造对象
{
if
(
single
==
null
)
//关键点2:判断单例对象是否已经被构造
{
lock
(
obj
)
//关键点3:加线程锁
{
if
(
single
==
null
)
//关键点4:二次判断单例是否已经被构造
{
single
=
new
Singleton
(
)
;
}
}
}
return
single
;
}
}
|
所以,在判断单例实例是否被构造时,需要检测两次,在线程锁之前判断一次,在线程锁之后判断一次,再去构造实例,这样就万无一失了。是不是很简(Tu)单(Xue)呢?
最后,我们来归纳一下。下次面试别人再问你单例模式,你可以这样说:
单例是为了保证系统中只有一个实例,其关键点有5
一.私有构造函数
二.声明静态单例对象
三.构造单例对象之前要加锁(lock一个静态的object对象)
四.需要两次检测单例实例是否已经被构造,分别在锁之前和锁之后
如果要你撸代码,你就撸最后这一段,完美~~~面试官要是个女的准想和你生猴子。。。
哦,别高兴太早了,面试官要是个男的,也可能会问你下列问题
0.为何要检测两次?
如上面所述,有可能延迟加载或者缓存原因,造成构造多个实例,违反了单例的初衷。
1.构造函数能否公有化?
不行,单例类的构造函数必须私有化,单例类不能被实例化,单例实例只能静态调用
2.lock住的对象为什么要是object对象,可以是int吗?
不行,锁住的必须是个引用类型。如果锁值类型,每个不同的线程在声明的时候值类型变量的地址都不一样,那么上个线程锁住的东西下个线程进来会认为根本没锁,相当于每次都锁了不同的门,没有任何卵用。而引用类型的变量地址是相同的,每个线程进来判断锁多想是否被锁的时候都是判断同一个地址,相当于是锁在通一扇门,起到了锁的作用。
好了,这下估计男的都想跟你生猴子了。。。