【Java】多线程输出滞后/错误解决&&线程创建方式与原理

【Java】多线程输出滞后/错误解决&线程创建方式与原理

输出缓冲区

join与sleep因输出缓冲区导致的控制台打印信息混乱

join():等别人做完/做一段时间,我再做(控制顺序);

sleep():自己歇会儿,到点再做(控制间隔 / 休眠)。

下面是一段关于join和sleep的测试代码:

java 复制代码
package day14;

public class JoinTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i=0;i<100;i++){
                    if (i==90){
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println("线程A--"+i+"-"+System.currentTimeMillis());
//                    System.out.flush();
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i=0;i<100;i++){
                    if (i==30){
                        try {
                            t1.join(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println("线程B--"+i+"-"+System.currentTimeMillis());
//                    System.out.flush();
                }
            }
        };
        t1.start();
        t2.start();
    }
}

根据最终输出的结果大概率可以看到一个错误:也就是时间戳靠后的先输出,时间戳靠前的后输出,很显然这符合实际情况。这种bug的出现正是因为输出缓冲区。

什么是输出缓冲区&多线程输出滞后/错误解决

Java 中 System.out 是 PrintStream 类型的输出流,默认开启缓冲机制:

执行 System.out.println()/print() 时,内容不会直接打印到控制台,而是先存入 JVM 内存中的「输出缓冲区」;

只有满足以下条件之一,缓冲区内容才会刷新(flush) 到控制台:

1.缓冲区被写满(默认大小约 8KB);

2.调用 System.out.flush() 手动刷新;

3.输出流关闭(如程序结束时 System.out.close());

4.输出换行符(\n)时,PrintStream 会自动刷新(println() 包含 \n,但仅对控制台输出生效,文件输出不生效)。

由此,要解决多线程中输出错误/滞后问题只需要再输出语句后添加:

java 复制代码
System.out.flush()

多线程基础

两种线程创建方式与对比

方式一:继承 Thread 类

java 复制代码
//定义线程类
class 线程类名 extends Thread {
    @Override
    public void run() {
        // 线程执行逻辑(核心任务)
    }
}
//启动线程
public class Test {
    public static void main(String[] args) {
        线程类名 线程对象 = new 线程类名();
        线程对象.start(); // 必须调用start(),而非直接run()
    }
}

方式二:实现 Runnable 接口

java 复制代码
//定义任务类
class 任务类名 implements Runnable {
    @Override
    public void run() {
        // 任务执行逻辑(核心业务)
    }
}
//启动线程
public class Test {
    public static void main(String[] args) {
        // 1. 创建任务对象
        任务类名 任务对象 = new 任务类名();
        // 2. 将任务对象传入Thread构造器,创建线程
        Thread 线程1 = new Thread(任务对象, "线程名称1");
        Thread 线程2 = new Thread(任务对象, "线程名称2");
        // 3. 启动线程
        线程1.start();
        线程2.start();
    }
}

显然,从代码的简洁性来看方式一更见方便。

但是Java中只能单继承,当实际开发中线程类需要继承其他父类时,那么将无法继承Thread类而只能实现Runnable接口。

并且每个继承Thread类的子类所创建的对象无法直接共享线程资源,而通过Runnable接口来创建的线程对象可以直接共享Runnable接口实现类的资源。

进程同步

什么是共享资源

在编程、计算机系统和软件开发中,共享资源指的是能被多个进程、线程、协程,或多个程序 / 组件共同访问和使用的资源,简单说就是 "大家共用的东西"。

这些资源的核心特点是可被多执行单元访问,但如果不做合理的同步 / 控制,多个单元同时操作时会引发数据错乱、资源竞争、死锁等问题(也是在学习线程池时,线程安全问题的核心根源)。

相关推荐
dogstarhuang4 分钟前
从 0 到 1 搭建可收费的 API 开放平台(实战)
java·架构·api
VortMall44 分钟前
『平台去经营化』平台治理能力全新重构|VortMall微服务商城系统v1.3.10
java·大数据·微服务·商城系统·开源商城·vortmall·去经营化
Echo缘1 小时前
嵌入式系统C语言资源分类与内存分布分析
c语言·开发语言
吴声子夜歌1 小时前
Redis 3.x——集群故障转移
java·数据库·redis·集群
Java面试题总结1 小时前
mybatis插件
java·tomcat·mybatis
JaneConan1 小时前
鸿蒙 ArkUI 深水区:@Watch 和 @Observed,状态变了「自动跑」+ 嵌套对象「深层重绘」
开发语言·后端·ui·harmonyos
赤水无泪1 小时前
qt中图标、名称的设置方式
开发语言·qt
王莎莎-MinerU1 小时前
MCP 解决的是工具接入,科研 Agent 还缺的是科学证据接口标准化
开发语言·网络·人工智能·深度学习·pdf·c#·php
橘子海全栈攻城狮2 小时前
【最新源码】基于SpringBoot + Vue的超市管理系统的设计与实现D002
java·开发语言·vue.js·spring boot·后端·spring
smartvxworks2 小时前
Linux 实时内核(Linux Real-Time Kernel)详解:原理、实践与优化
java·linux·服务器