当前位置: 首页 > news >正文

System.out源码解读——err 和 out 一起用导致的顺序异常Bug

前言

笔者在写一个小 Demo 的过程中,发现了一个奇怪的问题。问题如下:

// 当 flag=true 时打印 a1 ;当 flag=false 时打印 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()在笔者看来要分成两部分:

  1. System.out;
  2. 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 的源码后,我就在思考,会不会是两种不同的输出流导致线程冲突了。于是我使用 setErrerr 的输出流重定向为 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。

http://www.lryc.cn/news/441194.html

相关文章:

  • 汽车软件开发之敏捷开发
  • ListBox显示最新数据、左移和右移操作
  • mysql实用系列:日期格式化
  • 时钟频率、AI采样率与AO更新率的关系
  • 代理IP设置后IP不变?可能的原因及解决方法
  • 瑞芯微RK3588开发板Linux系统添加自启动命令的方法,深圳触觉智能Arm嵌入式鸿蒙硬件方案商
  • Varjo在芬兰开设新工厂,以满足国防部门在XR模拟训练中的需求
  • python 识别省市、区县并组建三级信息数据库
  • 家用小型超声波清洗机怎么选?四大人气爆款品牌不可错过!
  • NVIDIA最新AI论文介绍NEST:一种用于语音处理的快速高效自监督模型
  • 聊聊对别人表示真正的关注
  • 大数据-133 - ClickHouse 基础概述 全面了解
  • 第1步win10宿主机与虚拟机通过NAT共享上网互通
  • Python学习——【2.3】for循环
  • Element UI:初步探索 Vue.js 的高效 UI 框架
  • React Native防止重复点击
  • 如何将Git本地代码推送到Gitee云端仓库
  • 架构师论文备考-论云原生架构及其应用
  • 12.java面向对象:java构造器
  • 微服务之间的安全通信
  • 全面掌握 Jest:从零开始的测试指南(下篇)
  • 如何利用UML进行领域建模
  • Vue实用操作篇-1-第一个 Vue 程序
  • Qwen2-VL的微调及量化
  • [数据集][目标检测]车窗状态检测车窗开关检测数据集VOC+YOLO格式299张3类别
  • 自动泊车系统中的YOLOv8 pose关键点车位线检测
  • Java html生成pdf和图片
  • JavaWeb笔记整理——Redis
  • 数据库(mysql)常用命令
  • 源网荷储一体化新型电力系统解决方案