单例模式概念
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。
单例模式有以下特点
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
单例模式的使用
本章节主要用日志输出工具类作为演示讲解:
现在有个需求需要编写一个日志工具类来控制不同级别的日志输出
- 版本1.0
public class LogUtil {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static final int LEVEL = VERBOSE;
public void v(String tag, String msg) {
if (LEVEL <= VERBOSE) {
Log.v(tag, msg);
}
}
public void d(String tag, String msg) {
if (LEVEL <= DEBUG) {
Log.d(tag, msg);
}
}
public void i(String tag, String msg) {
if (LEVEL <= INFO) {
Log.i(tag, msg);
}
}
public void w(String tag, String msg) {
if (LEVEL <= WARN) {
Log.w(tag, msg);
}
}
public void e(String tag, String msg) {
if (LEVEL <= ERROR) {
Log.e(tag, msg);
}
}
}
通过这个类来打印日志,只需要控制level的级别,就可以自由地控制打印的内容。比如现在项目处于开发阶段,就将level设置为DEBUG,这样所有的日志信息都会被打印。而项目如果上线了,可以把level设置为INFO,这样就只能看到INFO及以上级别的日志打印。如果你只想看到错误日志,就可以把level设置为ERROR。而如果你开发的项目是客户端版本,不想让任何日志打印出来,可以将level设置为NOTHING。打印的时候只需要调用:new LogUtil().debug("Hello World");
虽然这个工具好用,可是打印这种事情是不区分对象的,这里每次需要打印日志的时候都需要new出一个新的LogUtil,太占用内存了,所以将这个工具改成用单例模式实现。
- 版本2.0
public class LogUtil {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static final int LEVEL = VERBOSE;
private LogUtil() {
}
public static LogUtil getInstance() {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
return sLogUtil;
}
public void v(String tag, String msg) {
if (LEVEL <= VERBOSE) {
Log.v(tag, msg);
}
}
public void d(String tag, String msg) {
if (LEVEL <= DEBUG) {
Log.d(tag, msg);
}
}
public void i(String tag, String msg) {
if (LEVEL <= INFO) {
Log.i(tag, msg);
}
}
public void w(String tag, String msg) {
if (LEVEL <= WARN) {
Log.w(tag, msg);
}
}
public void e(String tag, String msg) {
if (LEVEL <= ERROR) {
Log.e(tag, msg);
}
}
}
首先将LogUtil的构造函数私有化,这样就无法使用new关键字来创建LogUtil的实例了。然后使用一个sLogUtil私有静态变量来保存实例,并提供一个公有的getInstance方法用于获取LogUtil的实例,在这个方法里面判断如果sLogUtil为空,就new出一个新的LogUtil实例,否则就直接返回sLogUtil。这样就可以保证内存当中只会存在一个LogUtil的实例了。单例模式完工!这时打印日志的代码需要改成如下方式:LogUtil.getInstance().debug("Hello World");
单例模式代码:
public static LogUtil getInstance() {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
return sLogUtil;
}
版本2.0的单例模式代码确实现了单例模式但是如果现在有两个线程同时在执行getInstance方法,第一个线程刚执行完第2行,还没执行第3行,这个时候第二个线程执行到了第2行,它会发现sLogUtil还是null,于是进入到了if判断里面。这样单例模式就失败了,因为创建了两个不同的实例。
- 版本3.0
public synchronized static LogUtil getInstance() {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
return sLogUtil;
}
直接在单例模式的代码上加上同步锁,这样看来版本2.0的问题基本解决了,可是存在一个BUG,不知大家发现没有,如果是在getInstance方法上加了一个synchronized,那么我每次去执行getInstace方法的时候都会受到同步锁的影响,这样运行的效率会降低,其实只需要在第一次创建LogUtil实例的时候加上同步锁就好了。
- 版本4.0
public static LogUtil getInstance() {
if (sLogUtil == null) {
synchronized (LogUtil.class) {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
}
}
return sLogUtil;
}
代码改成这样之后,只有在sLogUtil还没被初始化的时候才会进入到第3行,然后加上同步锁。等sLogUtil一但初始化完成了,就再也走不到第3行了,这样执行getInstance方法也不会再受到同步锁的影响,效率上会有一定的提升。这种方法叫做双重锁定(Double-Check Locking)。
单例模式现在完成了最后贴上完整的代码
package com.cc.cc.util;
import android.util.Log;
/**
* Created by CC on 2016/12/8.
* Log日志帮助类 项目发布后屏蔽日志输出
* 单例模式保证只有一个LogUtil对象
*/
public class LogUtil {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static final int LEVEL = VERBOSE;
private LogUtil() {
}
private static LogUtil sLogUtil;
public static LogUtil getInstance() {
if (sLogUtil == null) {
synchronized (LogUtil.class) {
if (sLogUtil == null) {
sLogUtil = new LogUtil();
}
}
}
return sLogUtil;
}
public void v(String tag, String msg) {
if (LEVEL <= VERBOSE) {
Log.v(tag, msg);
}
}
public void d(String tag, String msg) {
if (LEVEL <= DEBUG) {
Log.d(tag, msg);
}
}
public void i(String tag, String msg) {
if (LEVEL <= INFO) {
Log.i(tag, msg);
}
}
public void w(String tag, String msg) {
if (LEVEL <= WARN) {
Log.w(tag, msg);
}
}
public void e(String tag, String msg) {
if (LEVEL <= ERROR) {
Log.e(tag, msg);
}
}
}
使用的时候也比较简单:
LogUtil.getInstance().d("TAG","Hello World!");
一个完善的单例模式日志输出帮助类完成了,代码比较完善,有任何问题欢迎留言交流。