浅谈Java编程中的单例设计模式
写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上线后还可以帮助你分析数据。但是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 DEBUG = 0; public final int INFO = 1; public final int ERROR = 2; public final int NOTHING = 3; public int level = DEBUG; public void debug(String msg) { if (DEBUG >= 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); } } }
new LogUtil().debug("Hello World!");
可是没过多久,你的leader找到你来反馈问题了。他说虽然这个工具好用,可是打印这种事情是不区分对象的,这里每次需要打印日志的时候都需要new出一个新的LogUtil,太占用内存了,希望你可以将这个工具改成用单例模式实现。 你认为你的leader说的很有道理,而且你也正想趁这个机会练习使用一下设计模式,于是你写出了如下的代码(ps:这里代码是我自己实现的,而且我开始确实没注意线程同步问题): public class LogUtil { private static LogUtil logUtilInstance; public final int DEBUG = 0; public final int INFO = 1; public final int ERROR = 2; public final int NOTHING = 3; public int level = DEBUG; private LogUtil() { } public static LogUtil getInstance() { if (logUtilInstance == null) { logUtilInstance = new LogUtil(); } return logUtilInstance; } public void debug(String msg) { if (DEBUG >= 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); } } public static void main(String[] args) { LogUtil.getInstance().debug("Hello World!"); } }
LogUtil.getInstance().debug("Hello World");
你的leader提示你,使用单例模式就是为了让这个类在内存中只能有一个实例的,可是你有考虑到在多线程中打印日志的情况吗?如下面代码所示: public static LogUtil getInstance() { if (logUtilInstance == null) { logUtilInstance = new LogUtil(); } return logUtilInstance; }
public synchronized static LogUtil getInstance() { if (logUtilInstance == null) { logUtilInstance = new LogUtil(); } return logUtilInstance; }
你紧张了起来,怎么还会有问题啊? 你的leader笑笑:“不用紧张,这次不是bug,只是性能上可以优化一些。你看一下,如果是在getInstance方法上加了一个synchronized,那么我每次去执行getInstace方法的时候都会受到同步锁的影响,这样运行的效率会降低,其实只需要在第一次创建LogUtil实例的时候加上同步锁就好了。我来教你一下怎么把它优化的更好。” 首先将synchronized关键字从方法声明中去除,把它加入到方法体当中: public static LogUtil getInstance() { if (logUtilInstance == null) { synchronized (LogUtil.class) { if (logUtilInstance == null) { // 这里是必须的,因为有可能两个进程同时执行到synchronized之前 logUtilInstance = new LogUtil(); } } } return logUtilInstance; }
你的leader马上谦虚起来:“这种方法叫做双重锁定(Double-Check Locking),可不是我想出来的,更多的资料你可以在网上查一查。” 其实在java里实现单例我更习惯用饿汉模式 懒汉式的特点是延迟加载,实例直到用到的时候才会加载 饿汉式的特点是一开始就加载了,所以每次用到的时候直接返回即可(我更推荐这一种,因为不需要考虑太多线程安全的问题,当然懒汉式是可以通过上文所说的双重锁定解决同步问题的) 用饿汉式实现日志记录的代码如下: public class LogUtil { private static final LogUtil logUtilInstance = new LogUtil(); public final int DEBUG = 0; public final int INFO = 1; public final int ERROR = 2; public final int NOTHING = 3; public int level = DEBUG; private LogUtil() { } public static LogUtil getInstance() { return logUtilInstance; } public void debug(String msg) { if (DEBUG >= 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); } } public static void main(String[] args) { logUtil.getInstance().debug("Hello World!"); } }
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |