用单例模式编写一个通用日志工具类,深入理解Double-Check

       Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,原因是它的很多弊端,比如不可控制,所有的日志都会在项目上线后照常打印,从而降低运行效率;又或者不能将日志记录到本地文件,一旦打印被清除,日志将再也找不回来;再或者打印的内容没有Tag区分,你将很难辨别这一行日志是在哪个类里打印的。因此我们在日常开发的过程中通常是自己编写一个日志工具类。

       由于是一个工具类,因此考虑用单例模式实现。通常我们会用采用以下代码:

public class LogUtil {
 
	private static LogUtil sLogUtil;
 
	public final int DEBUG = 0;
 
	public final int INFO = 1;
 
	public final int ERROR = 2;
 
	public int level = DEBUG;
 
	private LogUtil() {
	}
 
	public static LogUtil getInstance() {
		if (sLogUtil == null) {
			sLogUtil = new LogUtil();
		}
		return sLogUtil;
	}
 
	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 synchronized static LogUtil getInstance() {
    if (sLogUtil == null) {
        sLogUtil = new LogUtil();
	}
	return sLogUtil;
}

       现在getInstance方法上加了一个synchronized,那么我每次去执行getInstace方法的时候都会受到同步锁的影响,但是这样运行的效率会降低,其实只需要在第一次创建LogUtil实例的时候加上同步锁就好了。因此进一步改进如下:

       首先将关键字从方法声明中去除,把它加入到方法体当中:

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方法也不会再受到同步锁的影响,效率上会有一定的提升。

        这种方法就叫做双重检查锁定(Double-Check Locking)

        当然,结合Android日志打印的特殊需求,我们再对该日志工具类做一些定制化的改写。让它可以输出线程和类的信息,并且让它只在测试环境下才进行打印。最终版本如下:

public class LogUtil {
    private static final String TAG = "LogUtil";

    private static boolean DEBUG = BuildConfig.DEBUG;

    private static LogUtil sLogUtil;

    private LogUtil() {}

    public static LogUtil getInstance() {
        if (sLogUtil == null) {
            synchronized (LogUtil.class) {
                if (sLogUtil == null) {
                    sLogUtil = new LogUtil();
                }
            }
        }
        return sLogUtil;
    }

    public static void info(Object msg) {
        if (DEBUG) {
            Log.i(TAG, formatLog(msg.toString()));
        }
    }

    public static void debug(Object msg) {
        if (DEBUG) {
            Log.d(TAG, formatLog(msg.toString()));
        }
    }

    public static void error(Object msg) {
        if (DEBUG) {
            Log.e(TAG, formatLog(msg.toString()));
        }
    }

    private static String getFuncName() {
        try {
            StackTraceElement[] sts = Thread.currentThread().getStackTrace();
            if (sts != null) {
                for (StackTraceElement st : sts) {
                    if (st.isNativeMethod()) {
                        continue;
                    }
                    if (st.getClassName().equals(Thread.class.getName())) {
                        continue;
                    }
                    if (st.getClassName().equals(LogUtil.class.getName())) {
                        continue;
                    }
                    return "Thread:" + Thread.currentThread().getName() + ", at " + st.getClassName() + "." + st.getMethodName()
                            + "(" + st.getFileName() + ":" + st.getLineNumber() + ")";
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String formatLog(String msgStr) {
        return getFuncName() + "----" + msgStr;
    }
}
发布了35 篇原创文章 · 获赞 37 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34519487/article/details/103887129