从零了解多线程(万字详解)

news2025/1/16 4:47:57

目录

为什么要引入多线程?

为什么线程更轻?

线程和进程的关系

多线程的弊端

Thread类

用Thread类创建一个线程并启动它

用一段通过多线程体现并发执行的效果

start和run的区别

使用jdl自带的工具包jconsole查看当前java进程中的所有线程

调用栈 

 注意:

jave中创建线程的方法

1.继承Thread,重写run

2.实现Runnable接口

3.使用匿名内部类,继承Thread

4.使用匿名内部类,实现Runnable

5.使用Lambda表达式(最常用的)

 Thread常见属性

前台线程和后台线程

isAlive()

isInterrupted()判断线程是否终止

interrupted

join()

sleep

当这个pcb回到就绪队列会被立即执行吗?

线程的状态

线程的几种状态:

多线程的意义


为什么要引入多线程?

多进程编程已经可以利用cpu的多核资源,解决并发编程了

但是进程太重了
创建一个进程,开销比较大
调度一个进程,开销也比较大
销毁一个进程,开销还是比较大

进程是操作系统资源分配的基本单位,
进程主要重在资源的开销和回收上

因此引入线程,线程也叫轻量进程
解决并发编程的前提下,让创建,销毁,调度的速度更快一些,提高程序执行效率

为什么线程更轻?

轻在把申请资源和释放资源的操作省下了.
举个例子
比如说现在有10个人要去饭店吃饭,有俩种方案
一种是10个人分别开10间包间吃饭
一种是10个人开1间包间吃饭
显然第一种开销更大,多花了许多的包间费
第二种开销更小,少花了许多包间费

线程和进程的关系

进程包含线程
一个进程包含1个或多个线程(不能1个都没有)
同一个进程中的线程之间,共用了进程的同一份资源(主要是内存(同1个进程中,线程1new的对象,线程2,3,4也可以用)和文件描述符(线程1打开的文件,线程2,3,4都可以直接使用))

线程是操作系统调度的基本单位
进程的调度相当于,每个进程只有1个线程这样的情况
如果1个线程有多个线程,每个线程都是独立在cpu上调度的

每个线程都有自己的执行逻辑.

一个核心上执行的是一个线程
如果一个进程有线程1和线程2
线程1可能在核心A上执行,
线程2可能在核心B上执行

一个线程也是通过PCB来描述的
1个进程里面可能对应一个PCB,也可能对应多个PCB,取决于这个进程中有多少个线程

PCB描述的特征里面,每个线程都有自己的调度属性(PCB的状态,上下文,优先级,记账信息)
各自记录各自的
但是同1个进程里面的PCB之间,pid是一样的,内存指针和文件描述符也是一样的

多线程的弊端

增加线程的数量,也不是可以一直提高速度
CPU核心数量有限,线程太多,不少的资源开销反而浪费在资源调度上面了

多线程的情况下,多个进程共享同一份资源空间,可能会发生冲突
线程1和线程2都想要同一份资源,此时就可能发生冲突,
可能会导致线程安全问题
在多进程中,就不会发生这种情况
如果一个线程抛异常,如果处理不好,可能就把整个线程带走了
这个进程中的其它线程也就挂了

Thread类

Thread类不需要import导入别的包,它是在java.long下面的,默认已经导入了

用Thread类创建一个线程并启动它

class MyThread extends Thread{
    @Override
    public void run() {
            System.out.println("hello world");
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

这样做和直接在main方法中打印hello world有什么区别?

如果直接在main方法中打印hello world,我们java进程中主要就是一个线程.
(调用main方法中的线程)主线程
而通过start(),主线程调用start()创建了一个新的线程,新的线程调用run方法

用一段通过多线程体现并发执行的效果

class MyThread extends Thread{
    @Override
    public void run() {
        while(true){
            //为了方便观察,此处使用sleep休眠1s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello thread");
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello main");
        }
    }
}

start和run的区别

start是真正创建了一个新的线程,线程是独立的执行流

run只是描述了线程干的活,并没有创建线程,如果直接在main方法中调用run,
此时没有创建新的线程,全是main一个人在干活

比如上面这个代码,把t.start,改成t.run结果截然不同

 

 

没有之前的main进程和t线程抢占式执行,因为此时只有main这1个进程.

使用jdl自带的工具包jconsole查看当前java进程中的所有线程

 

 

 

调用栈 

 

 注意:

new Thread对象操作,不创建线程(系统内核里的pcb)
调用start才创建pcb,才有真正的线程
PCB是一个数据结构,体现的是 进程/线程是如何实现的,如何被描述出来的

jave中创建线程的方法

1.继承Thread,重写run

class MyThread extends Thread{
    @Override
    public void run() {
            System.out.println("hello world");
    }
}

2.实现Runnable接口

class MyRunnable implements Runnable{
    @Override
    //Runnable 的作用,是描述一个"要执行的任务",run方法描述的是任务要干的活
    public void run() {
        System.out.println("hello thread");
    }
}
public class ThreadDemo2{
    public static void main(String[] args){
        //描述了一个任务
        Runnable runnable = new MyRunnable();
        //把任务交给线程来执行
        Thread t = new Thread(runnable);
        t.start();
    }
}

这样做的目的是为了解耦合,让线程和线程要做的任务分离开
如果将来要改代码,不用多线程,使用多进程,线程池......此时代码改动较少

3.使用匿名内部类,继承Thread

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        };
        t.start();
    }
}

1.创建了一个Thread子类(子类没有名字)
2.创建了子类的实例,并让t指向这个子类

4.使用匿名内部类,实现Runnable

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("hello thread");
            }
        });
        t.start();
    }
}

此处创建了一个类,实现了Runnable接口,同时创建了一个实例,并把这个实例传入到Thread的构造方法.

5.使用Lambda表达式(最常用的)

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello world");
        });
        t.start();
    }
}

把任务用Lambda表示
把Lambda传给Thread构造方法

Thread常见构造方法

Thread(Runnab tarfet,String name)

 

 Thread常见属性

 

前台线程和后台线程

前台线程:
手动创建的线程,默认是前台的,包括main默认也是前台线程
前台线程会阻止进程结束,前台线程的工作没做完,进程是结束不了的

后台线程:
其它的jvm自带的线程都是后台线程
可以使用setDaemon把线程设置成后台线程
后台线程不会阻止进程结束,后台线程的工作没做完,进程也是可以结束的

t本来是一个前台线程,如果t线程的任务不执行完,这个程序是不会结束的,会一直打印hello world
但是我们现在 把t线程设置成后台线程,t线程的任务还没执行完,程序也是可以结束的

isAlive()

在真正调用start之前,调用t.isAlive就是false
调用start之后,调用isAlive就是true

如果内核里线程把任务执行完了,此时线程销毁,pcb也随之释放,
但是Thread t这个对象不一定被释放
调用isAlive为false

public class ThreadDemo6 {
    public static void main(String[] args){
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                    System.out.println("hello world");
            }
        },"baiyang");
        t.start();
        while(true){
            try {
                Thread.sleep(1000);
                t.isAlive();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

 

isInterrupted()判断线程是否终止

线程终止:不是让线程立即终止,而是通知线程要终止了
线程是否终止,取决于线程的具体代码写法

此时线程可能立即终止
也可能等一会再终止
还可能忽略这个终止

1.使用标志位来控制线程是否要终止

public class ThreadDemo7 {
    private static boolean flag = true;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while(flag){
                System.out.println("hello world");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        flag = false;
    }
}

2.使用Thread自带的标志位,来判断isInterrupted()

public class ThreadDemo8 {
    public static void main(String[] args)  {
        Thread t = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
    }
}

 

interrupted

上面这个代码的运行结果是这样的:

触发异常后,程序任然继续执行

使用interrput后,会执行如下三步操作

1.把线程内部的标志位boolean设置为true
2.如果线程在进行sleep,就会触发异常,把sleep唤醒
3.sleep在唤醒的时候,还会把刚才设置的标志位boolean,再设置为false(清空标志位) 

这就导致了,当sleep异常被catch完了之后,循环继续执行

我们此时在打印异常后面,加一个break,结束,
就可以立即让线程t终止

我们还可以在catch后面加一些我们想要做的事
比如唤醒后,让线程等待1s,再打印个"彭于晏",才终止

 

所以interrupted清空标志位的目的是
为了让唤醒sleep之后,线程是立即终止
还是等一会再终止,还是忽略这个终止......
这个选择权交给我们自己

join()

join:等待一个线程

线程是一个随机调度的过程,
等待线程做的事就是,控制俩个线程结束的顺序

public class ThreadDemo9 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello thread");
        });
        t.start();
        System.out.println("join之前");
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("join之后");
    }
}

 

如果开始执行join的时候,t线程已经执行完毕,此时join不会阻塞,会立即返回

join的几个版本

sleep

 

休眠这个线程多少毫秒

让线程休眠,本质上就是让这个线程不去参与调度了(不去cpu上执行了)
 

pcb是使用链表的数据结构来组织的
但实际上,并不是一个简单的链表,而是一系列以链表为核心的数据结构

一旦线程进入了阻塞状态,对应的PCB就进入了阻塞队列了,此时线程暂时无法参与调度

比如调用sleep(1000),
对应的线程pcb就要再阻塞队列中待1s

当这个pcb回到就绪队列会被立即执行吗?

不一定,虽然是sleep(1000),但考虑到实际的调度开销,对应的线程无法在唤醒之后就被立即执行
它需要和其它在就绪队列的pcb一样,抢占cpu,实际时间间隔大于1s

线程的状态

状态是针对当前的线程调度描述的
线程是操作系统调度的基本单位,状态更适合线程

线程的几种状态:

1.NEW 创建了Thread对象,但还是没有调用start(内核还没有创建对应的pcb)

2.TERMINATED 表示内核中的pcb已经执行完毕(内核里的pcb已经销毁),但是Thread对象还在

3.RUNNABLE 可以运行的,包括正在cpu上执行的,和在就绪队列,随时可以去cpu上执行的

4.WAITING

5.TIMED_WAITING

6.BLOCKED

4,5,6都是阻塞(线程pcb在阻塞队列中),这几种状态是不同原因的阻塞

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 100_0000; i++) {
               //此处这个循环什么都不做
            }
        });
        //启动之前获取一下t的状态,也就是NEW状态
        System.out.println("start之前 "+t.getState());
        t.start();
        System.out.println("t线程正在执行中 "+t.getState());
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t结束之后 "+t.getState());
    }
}

 

 我们再在上面这个代码稍加改动

public class ThreadDemo10 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 100_0000; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //启动之前获取一下t的状态,也就是NEW状态
        System.out.println("start之前 "+t.getState());
        t.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("t线程正在执行中 "+t.getState());
        }
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t结束之后 "+t.getState());
    }
}

 

我们可以看到打印的t线程正在执行中的状态有,RUNNABLE,也有TIME_WAITING,
当t线程没有执行到sleep(10)的时候,就是RUNNABLE,比如正在执行for循环里面(int i = 0;i<100_0000;i++)这些
当t线程执行到sleep(10),t线程发生阻塞,此时再获取t线程的状态就是TIME_WAITING

多线程的意义

程序分成
CPU密集,包含大量的 加减乘除 等运算,
IO密集型,涉及到大量的读写操作,比如读写文件,读写控制台,读写网络......

写个代码感受一下多线程的意义

假设当前有俩个变量a,b,需要把俩个变量a,b各自自增100亿次(典型的CPU密集型场景),
串行执行:可以一个线程,先针对a自增,然后再针对b自增
并发执行:还可以俩个线程,线程1对a进行自增,线程2对b进行自增

1.串行执行

public class ThreadDemo11 {
    public static void main(String[] args) {
        serial();
    }
    //串行执行
    public static void serial(){
        //为了衡量代码的执行速度,加上计时操作
        //currentTimeMillis 获取当前系统的 ms 级时间戳
        long beg = System.currentTimeMillis();
        long a = 0;
        for(long i = 0;i < 100_0000_0000L;i++){
            a++;
        }
        long b = 0;
        for(long i = 0;i < 100_0000_0000L;i++){
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("串行耗时: "+(end - beg)+"ms");
    }
}

2.并发执行

public class ThreadDemo11 {
    public static void main(String[] args) {
        concurrency();
    }
    //多线程执行
    public static void concurrency(){
        //使用俩个线程分别完成自增
        Thread t1 = new Thread(()->{
            long a = 0;
            for(long i = 0;i < 100_0000_0000L;i++){
                a++;
            }
        });
        Thread t2 = new Thread(()->{
            long b = 0;
            for(long i = 0;i < 100_0000_0000L;i++){
                b++;
            }
        });
        //开始计时
        long beg = System.currentTimeMillis();
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
           e.printStackTrace();
        }
        //结束计时
        long end = System.currentTimeMillis();
        System.out.println("并发执行时间: "+(end - beg) +"ms");
    }
}

 

 

我们可以发先, 俩个线程并发执行,明显更快!

为什么不是刚好缩短到串行执行的一半?

t1和t2不一定都是分布在俩个cpu上执行
不能保证它俩一定是并行执行,也有可能是并发执行

另外,t1和t2在执行过程中,会经历很多次调度,
这些调度,有些是并发(在一个核心上执行),有些是并发(在俩个核心上执行的)
到底是多少次并发,多上次并行,取决于操作系统的配置,和当时程序运行的环境
此外,线程调度自身也是有时间消耗的

总结

因此多线程,在CPU密集的任务中,有非常大的作用,可以充分利用cpu的多核资源,加快程序运行效率
当然多线程在IO密集的任务中也是有作用的

不过使用多线程,也不一定就能提高效率
取决于,是否是多核cpu
当前核心是否空闲(如果这些cpu的核心都已经满载了,这个时候再多线程也没用,反而会花费更多的开销浪费在线程调度上)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/117420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Duplicate File Finder Pro - 重复文件查找器,给你的 Mac 清理出大量磁盘空间

Duplicate File Finder Pro - 重复文件查找器&#xff0c;给你的 Mac 清理出大量磁盘空间 重复文件查找器 Duplicate File Finder Pro 是一个实用程序&#xff0c;只需3次点击就能在Mac上找到重复的文件。拖放功能和尽可能多的文件夹&#xff0c;你想&#xff0c;然后按下扫描按…

理解Linux中的进程状态

文章目录运行状态阻塞状态挂起状态磁盘睡眠状态暂停状态追踪停止状态僵尸状态死亡状态孤儿状态Linux内核进程状态源代码一台电脑一般只有一个CPU、一个磁盘&#xff08;无论一台电脑有几个CPU、磁盘&#xff0c;数量都是远少于进程的&#xff0c;这里举例用一个&#xff09;。运…

2022年广西食品安全管理员模拟试题及答案

百分百题库提供食品安全管理员考试试题、食品安全管理员考试预测题、食品安全管理员考试真题、食品安全管理员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 一、单选题 1.&#xff08;重点&#xff09;《上海市集体用餐配…

深度对比MemcacheD 和 Redis,论你不知道的二三事

谈到深度对比MemcacheD 和 Redis&#xff0c;作为老牌程序员首先想到的就是Memcache好一点&#xff0c;毕竟更节省内存。还可以存图片视频之类的&#xff0c;大部分市面上的cache都是MemcacheD。 但是评价一款cache并不能只看这一方面不是~ 数据存储的安全系数 要知道&#…

2023春招面试题:Redis数据库面试题整理

redis是什么&#xff1f;&#xff08;必会&#xff09; Redis 是 C 语言开发的一个开源的&#xff08;遵从 BSD 协议&#xff09;高性能非关系型&#xff08;NoSQL&#xff09;的&#xff08;key-value&#xff09;键值对数据库。可以用作数据库、缓存、消息中间件等。 redis…

STM32F103学习

目录 1、框架了解 2.GPIO &#xff08;1&#xff09;开漏输出与推挽输出 &#xff08;2&#xff09;基本知识了解 HAL库函数&#xff1a; 配置流程&#xff1a; &#xff08;3&#xff09;按键实验&#xff1a;STM32的按键开发基础_哔哩哔哩_bilibili 3、时钟 时钟框图…

力扣(leetcode)经典题目分享第3期——栈和队列

栈和队列一. 选择题1.1 进出栈顺序1.2 循环队列1.3 队列的基本运算1.4 循环队列的有效长度二. OJ练习题2.1 括号匹配问题2.2 用队列实现栈2.3 用栈实现队列2.4 循环队列总结&#xff1a;一. 选择题 1.1 进出栈顺序 若进栈序列为 1,2,3,4 &#xff0c;进栈过程中可以出栈&…

unreal编译源码搭建dedicated server的流水账——但是细

参考视频&#xff1a; B站的视频&#xff1a; https://www.bilibili.com/video/BV1wk4y1m7wz/?spm_id_from333.337.search-card.all.click&vd_sourced33b44674c517c8b7928e8d3ac316b37 YouTube的视频&#xff1a; https://www.youtube.com/watch?vAKiGajA7AXM 和上面的视…

基于STM32CUBEMX驱动多个VL6180X

概述 VL6180X是基于ST FlightSense™专利技术的最新产品。这是一个突破性的技术&#xff0c;实现了独立于目标反射率的绝对距离测量。现有技术通过测量反射光的光量来估算距离&#xff0c;这种方法的最大缺点是被测物体的颜色和表面对测量精度影响很大&#xff0c;而VL6180X则…

云端办公后,协同软件也能轻松做好项目管理

最近很多朋友在后台问我&#xff0c;数字化移动办公环境下如何做好项目管理&#xff0c;但是问题不够聚焦&#xff0c;所以我决定从自己的理解出发&#xff0c;分享一下项目管理的一些心得。需要说明的是&#xff0c;传统项目管理和互联网项目管理存在很大的差异&#xff0c;尤…

MyBatis源码(二)如何执行sql

前言 接着environmentElement获取数据源信息后&#xff0c;同级执行代码的mappersElement。 Mybatis源码&#xff08;三&#xff09;如何操作数据库 MyBatis源码&#xff08;二&#xff09;如何执行sql Mybatis源码&#xff08;一&#xff09;获取数据源 结构小结 分析ma…

Leetcode 2. 两数相加(高精度加法模板)

Leetcode 2. 两数相加 题目 思路 链表从头开始存放数据的个位十位 百位新建一个链表C&#xff0c;将链表A和B每一个相加的结果存放在C中&#xff0c;注意加法的进位 代码 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode n…

实力领航|万应智谷云平台成功入选2022中国互联网大会“互联网助力经济社会数字化转型”特色案例

近日&#xff0c;以“发展数字经济 促进数字文明”为主题&#xff0c;由工业和信息化部、深圳市人民政府主办&#xff0c;中国互联网协会、广东省通信管理局、深圳市工业和信息化局等单位承办的2022&#xff08;第二十一届&#xff09;中国互联网大会在深圳国际会展中心召开。开…

【超多代码、超多图解】Node.js一文全解析

目录一、Node.js简介1.1什么是Node.js1.2 Node.js可以做什么1.3 Node.js的安装1.4 Node.js的使用二、模块化处理2.1 什么是模块化2.2 内置模块2.2.1 fs文件系统模块&#xff08;1&#xff09;基本用法&#xff08;2&#xff09;防止动态拼接(3)路径问题2.2.2 path内置模块2.2.3…

前缀函数与KMP算法

一&#xff0c;前缀函数 1&#xff0c;定义 该函数存储一个字符串的各个长度的子串真前缀与真后缀相等的长度&#xff08;注意&#xff0c;真前缀最多长度为n-1&#xff0c;不包含最后一个字符&#xff0c;真后缀同理&#xff0c;不包含第一个字符&#xff09; 用p[i]数组储…

【LeetCode每日一题】——744.寻找比目标字母大的最小字母

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 二分查找 二【题目难度】 简单 三【题目编号】 744.寻找比目标字母大的最小字母 四【题目描述…

【小5聊】基础算法 - 实现字符串1到N位长度的组合关键词

在本篇文章中&#xff0c;我们讲一起了解下基础算法的运用 在程序开发里&#xff0c;算法无处不在&#xff0c;掌握算法才能更好的提高程序效率和质量 【算法返回效果】 【实现的功能描述】 当前算法主要实现输入一定长度的字符串后&#xff0c;能够返回按顺序1个字符长度、…

第二证券|七位投资专家指点2023 战略性看好A股 市场将提供更多机会

2022年行将收官&#xff0c;2023年新征途行将开启。 阅历了本年的一波三折、震动大跌&#xff0c;2023年A股商场将怎么演绎&#xff1f;有哪些时机值得注重&#xff1f;哪些危险要素需求留意&#xff1f; 对此&#xff0c;我国基金报记者专访了来自公募、券商资管、私募的七位…

Vjudge如何绑定洛谷账号

因为洛谷不支持Vjudge的bot提交&#xff0c;一个学弟问我才发现。要绑定洛谷账号才能在vj本地提交&#xff0c;绑定的方法也很奇怪&#xff0c;方法如下。 在题目来自洛谷的题目界面点击提交&#xff0c;出现如下界面。 发现没有bot提交选项&#xff0c;只能选择My Account,点…

NetInside网络攻击分析帮您轻松发现网络异常

分析概述 分析概述从以下四点做介绍。 故障信息来源 根据网络管理老师提供信息&#xff0c;29日上午7点半到9点时分&#xff0c;网络出现过故障。 分析对象 使用NetInside全流量分析系统对改故障进行分析。 分析思路 1、对比分析故障时段与非故障时段总流量信息。 2、对…