网站运营计划,百度推广获客,绵阳网站建设网站建设,个人营销型网站前言
笔者在写一个小 Demo 的过程中#xff0c;发现了一个奇怪的问题。问题如下#xff1a;
// 当 flagtrue 时打印 a1 #xff1b;当 flagfalse 时打印 a2。
public static void main(String[] args) {boolean flag false;for (int i 0; i 10; i) {if (flag) {Sys…前言
笔者在写一个小 Demo 的过程中发现了一个奇怪的问题。问题如下
// 当 flagtrue 时打印 a1 当 flagfalse 时打印 a2。
public static void main(String[] args) {boolean flag false;for (int i 0; i 10; i) {if (flag) {System.out.println(a1);flag false;} else {System.err.println(a2);flag true;}}} 但当我们运行的时候会发现控制台输出的顺序并不是我们所想的那样 而真正我们理想状态下的顺序应该是此输出顺序需要我们打断点放慢程序执行速度 笔者也是第一次注意到这个问题所以也着实是摸不着头绪。不知道为什么会出现这种问题。那么就来阅读 System.out的源码来分析下这种问题。 一、out 和 err 的定义
JDK文档对两者的解释
out“标准”输出流。此流已打开并准备接受输出数据。通常此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。err“标准”错误输出流。此流已打开并准备接受输出数据。通常此流对应于显示器输出或者由主机环境或用户指定的另一个输出目标。按照惯例此输出流用于显示错误消息或者显示那些即使用户输出流变量 out 的值已经重定向到通常不被连续监视的某一文件或其他目标也应该立刻引起用户注意的其他信息。 二、源码解读
首先System.out.println()在笔者看来要分成两部分
System.out;out.println(); 我们点进去out发现它其实就是System类的静态成员属性类型为PrintStream。是一种系统自带的输出流。 /*** “标准”输出流。此流已打开并准备接受输出数据。* 通常此流对应于显示输出或主机环境或用户指定的另一个输出目的地。* 如果Console存在则从字符到字节的转换中使用的编码等效于Console. charset* 否则等效于stdout.encoding。* See the {code println} methods in class {code PrintStream}.*/public static final PrintStream out null;
那我们发现out这个流被static final修饰那么我们就可以直接通过类名.属性 (System.out)的方式来访问。同时System这个类最上方有一块 静态代码块 用于初始化资源。会在程序被加载的时候最先调用。 private static native void registerNatives();// 程序运行的时候最先加载。registerNatives() 方法是一个本地方法。
// 作用就是通过静态初始化器初始化资源。
static {registerNatives();
}
这就是初始化后的效果 那我们再来看下println方法。println方法在PrintStream这个类中。 提供了多种重构形式 笔者以我们最常用的println(String x)为例带大家看看源码。
// 打印一个字符串然后终止该行
public void println(String x) {// 判断是否是由 PrintStream 来调用。// 如果使用 System.out 的方法来调用则永远为 True。if (getClass() PrintStream.class) {writeln(String.valueOf(x));} else {// 同步代码块保证同一时间只有一个线程进入。synchronized (this) {// 底层也是调用 implWriteln()print(x);newLine();}}
}// 如果判断为 True则走此方法进行打印
private void writeln(char[] buf) {try {// 首先判断成员属性 lock 是否已被初始化。在 System.out 时就被静态代码块创建了if (lock ! null) {lock.lock(); // 加锁保证线程安全try {implWriteln(buf);} finally {lock.unlock(); // 解锁}} else {// 如果 lock 没有被初始化则无法使用此锁。需要使用同步代码块来加锁synchronized (this) {implWriteln(buf);}}}catch (InterruptedIOException x) {Thread.currentThread().interrupt();}catch (IOException x) {trouble true;}
}// 底层真正输出的实现方法
private void implWriteln(char[] buf) throws IOException {ensureOpen(); // 检查当前输出流确保没有被关闭// textOut 和 charOut是跟踪文本和字符输出流以便在不刷新整个流的情况下刷新它们的缓冲区。textOut.write(buf); // 输出字节数组textOut.newLine(); // 输出换行符号帮助我们换行// 刷新流保证不会有元素还在缓存区没输出// 底层其实就是判断当前流中的元素是否 0如果 ! 0 则调用 write() 方法在输出一次。textOut.flushBuffer();charOut.flushBuffer();if (autoFlush)out.flush();
} 问题原因及解决办法
当时阅读完 out 的源码后我就在思考会不会是两种不同的输出流导致线程冲突了。于是我使用 setErr 将 err 的输出流重定向为 out。再试一次结果
public static void main(String[] args) {boolean flag false;for (int i 0; i 10; i) {if (flag) {System.out.println(a1);flag false;} else {System.setErr(System.out); // 就是这句话System.err.println(a2);flag true;}}} 我们发现确实输出顺序异常的问题解决了。那就证明我们的思路没问题。
但还是有问题System.err打印不出红色。
这个原因我查了一下System.err.println只能在屏幕上实现打印即使你重定向了也一样。
总结
System.out.println()的性能并不好。当我们深入分析时其调用顺序如下 println - print - write newLine。这个顺序流是Sun / Oracle JDK的实现。write和newLine都包含一个synchronized块。同步有一点开销但更多的是添加字符到缓冲区和打印的开销更大。
无论如何请勿使用System.out.println打印日志 下篇文章将带大家探究为什么 System.out 和 System.err 一起运行会导致顺序异常的 Bug。