快捷搜索:

Java设计模式分析之单例(Singleton)

写软件的时刻常常必要用到打印日志功能,可以赞助你调试和定位问题,项目上线后还可以赞助你阐发数据。然则Java原生带有的System.out.println()措施却很少在真正的项目开拓中应用,以致像findbugs等代码反省对象还会觉得应用System.out.println()是一个bug。

为什么作为Java新手神器的System.out.println(),到了真正项目开拓傍边会被藐视呢?着实只要细细阐发,你就会发明它的很多弊端。比如弗成节制,所有的日志都邑在项目上线后照常打印,从而低落运行效率;又或者不能将日志记录到本地文件,一旦打印被清除,日志将再也找不回来;再或者打印的内容没有Tag区分,你将很难辨别这一行日志是在哪个类里打印的。

你的leader也不是傻瓜,用System.out.println()的各项弊端他也清清楚楚,是以他本日给你的义务便是制作一个日志对象类,来供给更好的日志功能。不过你的leader人还不错,并没让你一开始就实现一个具备各项功能的牛逼日志对象类,只必要一个能够节制打印级其余日志对象就好。

这个需求对你来说并不难,你立即就开始着手编写了,并很快完成了第一个版本:

public class LogUtil {

public final int DEGUB = 0;

public final int INFO = 1;

public final int ERROR = 2;

public final int NOTHING = 3;

public int level = DEGUB;

public void debug(String msg) {

if (DEGUB >= level) {System.out.println(msg);

}}

public void info(String msg) {

if (INFO >= level) {System.out.println(msg);

}}

public void error(String msg) {

if (ERROR >= level) {System.out.println(msg);

}}

}

经由过程这个类来打印日志,只必要节制level的级别,就可以自由地节制打印的内容。比如现在项目处于开拓阶段,就将level设置为DEBUG,这样所有的日志信息都邑被打印。而项目假如上线了,可以把level设置为INFO,这样就只能看到INFO及以上级其余日志打印。假如你只想看履新错日志,就可以把level设置为ERROR。而假如你开拓的项目是客户端版本,不想让任何日志打印出来,可以将level设置为NOTHING。打印的时刻只必要调用:

new LogUtil().debug("Hello World");

你迫在眉睫地将这个对象先容给你的leader,你的leader听完你的先容后说:“好样的,往后大年夜伙都用你写的这个对象来打印日志了!”

可是没过多久,你的leader找到你来反馈问题了。他说虽然这个对象好用,可是打印这种工作是不区分工具的,这里每次必要打印日志的时刻都必要new出一个新的LogUtil,太占用内存了,盼望你可以将这个对象改成用单例模式实现。

你觉得你的leader说的很有事理,而且你也正想趁这个时机演习应用一下设计模式,于是你写出了如下的代码:

public class LogUtil {

private static LogUtil sLogUtil;

public final int DEGUB = 0;

public final int INFO = 1;

public final int ERROR = 2;

public final int NOTHING = 3;

public int level = DEGUB;

private LogUtil() {

}

public static LogUtil getInstance() {if (sLogUtil == null) {

sLogUtil = new LogUtil();}

return sLogUtil;}

public void debug(String msg) {

if (DEGUB >= level) {System.out.println(msg);

}}

public void info(String msg) {

if (INFO >= level) {System.out.println(msg);

}}

public void error(String msg) {

if (ERROR >= level) {System.out.println(msg);

}}

}

首先将LogUtil的构造函数私有化,这样就无法应用new关键字来创建LogUtil的实例了。然后应用一个sLogUtil私有静态变量来保存实例,并供给一个公有的getInstance措施用于获取LogUtil的实例,在这个措施里面判断假如sLogUtil为空,就new出一个新的LogUtil实例,否则就直接返回sLogUtil。这样就可以包管内存傍边只会存在一个LogUtil的实例了。单例模式竣工!这时打印日志的代码必要改成如下要领:

LogUtil.getInstance().debug("Hello World");

你将这个版本展示给你的leader瞧,他看后笑了笑,说:“虽然这看似是实现了单例模式,可是还存在着bug的哦。

你满腹困惑,单例模式不都是这样实现的吗?还会有什么bug呢?

你的leader提示你,应用单例模式便是为了让这个类在内存中只能有一个实例的,可是你有斟酌到在多线程中打印日志的环境吗?如下面代码所示:

public static LogUtil getInstance() {

if (sLogUtil == null) {sLogUtil = new LogUtil();

}return sLogUtil;

}

假如现在有两个线程同时在履行getInstance措施,第一个线程刚履行完第2行,还没履行第3行,这个时刻第二个线程履行到了第2行,它会发明sLogUtil照样null,于是进入到了if判断里面。这样你的单例模式就掉败了,由于创建了两个不合的实例。

你恍然大年夜悟,不过你的思维异常快,立即就想到了办理法子,只必要给措施加上同步锁就可以了,代码如下:

public synchronized static LogUtil getInstance() {

if (sLogUtil == null) {sLogUtil = new LogUtil();

}return sLogUtil;

}

这样,同一时候只容许有一个线程在履行getInstance里面的代码,这样就有效地办理了上面会创建两个实例的环境。

你的leader看了你的新代码后说:“恩,不错。这确凿办理了有可能创建两个实例的环境,然则这段代码照样有问题的。”

你首要了起来,怎么还会有问题啊?

你的leader笑笑:“不用首要,此次不是bug,只是机能上可以优化一些。你看一下,假如是在getInstance措施上加了一个synchronized,那么我每次去履行getInstace措施的时刻都邑受到同步锁的影响,这样运行的效率会低落,着实只必要在第一次创建LogUtil实例的时刻加上同步锁就好了。我来教你一下怎么把它优化的更好。”

首先将synchronized关键字从措施声明中去除,把它加入到措施体傍边:

public static LogUtil getInstance() {

synchronized (LogUtil.class) {if (sLogUtil == null) {

sLogUtil = new LogUtil();}

return sLogUtil;}

}

这样效果是和直接在措施上加synchronized完全同等的。然后在synchronized的外貌再加一层判断,如下所示:

public static LogUtil getInstance() {

if (sLogUtil == null) {synchronized (LogUtil.class) {

if (sLogUtil == null) {sLogUtil = new LogUtil();

}}

}return sLogUtil;

}

代码改成这样之后,只有在sLogUtil还没被初始化的时刻才会进入到第3行,然后加上同步锁。等sLogUtil一但初始化完成了,就再也走不到第3行了,这样履行getInstance措施也不会再受到同步锁的影响,效率上会有必然的提升。

你不由自立齰舌到,这措施真奇妙啊,能想得出来其实是太聪清楚明了。

你的leader顿时谦善起来:“这种措施叫做双重锁定(Double-Check Locking),可不是我想出来的,更多的资料你可以在网上查一查。”

单例:包管一个类仅有一个实例,并供给一个造访它的全局造访点。

您可能还会对下面的文章感兴趣: