day11-特殊文件、日志技术、多线程

YangeIT大约 33 分钟JavaSe特殊文件Properties文件日志多线程

day11-特殊文件、日志技术、多线程

跳转到页底(每日作业)[1]

今日目标

  • 特殊文件
    • Properties文件 注意格式
    • XML文件
      • XML作用 🍐
      • XML解析 🚀 了解即可
  • 日志文件
    • 日志作用 🍐
    • Logback框架 🚀
  • 多线程
    • 多线程创建方式 🍐 ✏️ ❤️

前置知识

  1. 能使用字节流或者工具类读取文件
  2. 能使用HashMap存储双列数据
  3. 使用过sout快捷键
  4. 听说过三个臭皮匠顶个诸葛亮的典故

一、属性文件 🚩 🍐

1️⃣ 1.1 特殊文件概述

普通的文本文件,没有任何规律可言,不方便程序对文件中的数据信息处理 不方便解析

1667990938340
1667990938340

特殊的文本文件 格式固定,方便解析

1667991153379
1667991153379
  • 后缀为.properties的文件,称之为属性文件,它可以很方便的存储一些类似于键值对的数据。经常当做软件的配置文件使用。
  • xml文件能够表示更加复杂的数据关系,比如要表示多个用户的用户名、密码、家乡、性别等。在后面,也经常当做软件的配置文件使用。

特殊的文件,主要学习以下的三点

1667991441046
1667991441046

2️⃣ 1.2 Properties属性文件

Properties是Map接口下面的一个实现类,所以Properties也是一种双列集合,用来存储键值对

Properties格式:

  1. 属性文件后缀以.properties结尾
  2. 属性文件里面的每一行都是一个键值对,键和值中间用=隔开。比如: admin=123456
  3. #表示这样是注释信息,是用来解释这一行配置是什么意思。
  4. 每一行末尾不要习惯性加分号,以及空格等字符;不然会把分号,空格会当做值的一部分。
  5. 键不能重复,值可以重复
点击查看图示
1667992083258
1667992083258

Properties核心作用:

Properties类的对象,用来表示属性文件,可以用来读取属性文件中的键值对。


使用Properties读取属性文件中的键值对,需要用到的方法如下。

1667992486134
1667992486134

Properties读取属性文件的步骤如下

  1. 创建一个Properties的对象出来(键值对集合,空容器)
  2. 调用load(字符输入流/字节输入流)方法,开始加载属性文件中的键值对数据到properties对象中去
  3. 调用getProperty(键)方法,根据键取值
点击查看代码

代码如下:

/**
 * 目标:掌握使用Properties类读取属性文件中的键值对信息。
 */
public class PropertiesTest1 {
    public static void main(String[] args) throws Exception {
        // 1、创建一个Properties的对象出来(键值对集合,空容器)
        Properties properties = new Properties();
        System.out.println(properties);

        // 2、开始加载属性文件中的键值对数据到properties对象中去
        properties.load(new FileReader("properties-xml-log-app\\src\\users.properties"));
        System.out.println(properties);

        // 3、根据键取值
        System.out.println(properties.getProperty("赵敏"));
        System.out.println(properties.getProperty("张无忌"));

        // 4、遍历全部的键和值。
        //获取键的集合
        Set<String> keys = properties.stringPropertyNames();
        for (String key : keys) {
            //再根据键获取值
            String value = properties.getProperty(key);
            System.out.println(key + "---->" + value);
        }
		
        properties.forEach((k, v) -> {
            System.out.println(k + "---->" + v);
        });
    }
}

使用Properties往属性文件中写键值对,需要用到的方法如下

1667993320872
1667993320872

往Properties属性文件中写键值对的步骤如下

  1. 先准备一个.properties属性文件,按照格式写几个键值对
  2. 创建Properties对象出来,
  3. 调用setProperty存储一些键值对数据
  4. 调用store(字符输出流/字节输出流, 注释),将Properties集合中的键和值写到文件中
    • 注意:第二个参数是注释,必须得加;

先准备一个users.properties属性文件,如下图所示

1667993682237
1667993682237

接下来,编写代码读取上面的属性文件。代码如下:

点击查看代码
public class PropertiesTest2 {
    public static void main(String[] args) throws Exception {
        // 1、创建Properties对象出来,先用它存储一些键值对数据
        Properties properties = new Properties();
        properties.setProperty("张无忌", "minmin");
        properties.setProperty("殷素素", "cuishan");
        properties.setProperty("张翠山", "susu");

        // 2、把properties对象中的键值对数据存入到属性文件中去
        properties.store(new FileWriter("properties-xml-log-app/src/users2.properties")
                         , "i saved many users!");

    }
}

运行上面的代码,user2.properties 配置文件打开效果如下图所示。

1667993581146
1667993581146

二、XML文件 🚩 🍐 🚀

1️⃣ 2.1 XML文件概述 🍐

相关信息

首先,我们来认识一下,什么是XML?

XML是可扩展的标记语言,意思是它是由一些标签组成 的,而这些标签是自己定义的。本质上一种数据格式,可以用来表示复杂的数据关系。

XML文件有如下的特点:

  • XML中的<标签名> 称为一个标签或者一个元素,一般是成对出现的。
  • XML中的标签名可以自己定义(可扩展) ,但是必须要正确的嵌套
  • XML中只能有一个根标签
  • XML标准中可以有属性
  • XML必须第一行有一个文档声明 ,格式是固定的<?xml version="1.0" encoding="UTF-8"?>
  • XML文件必须是以.xml为后缀结尾

如下图所示

1667993965682
1667993965682

2️⃣ 2.2 XML解析 🍐 🚀

使用程序读取XML文件中的数据,称之为XML解析

不需要我们自己写IO流代码去读取xml文件中的数据,直接使用现成的XML解析框架(如:DOM4J)

1667996374837
1667996374837

由于DOM4J是第三方提供的,所以需要把第三方提供的Jar包导入到自己的项目中来,才可以使用。具体步骤如下:

1667996538290
1667996538290

DOM4J解析XML文件的思想是: 文档对象模型(意思是把整个XML文档、每一个标签、每一个属性都等都当做对象来看待)。

  • Dowument对象表示整个XML文档
  • Element对象表示标签(元素)
  • Attribute对象表示属性
  • 标签中的内容就是文本

DOM4J解析XML需要用到的方法如下图所示

1667996750188
1667996750188

XML解析的过程,是从根元素开始,从外层往里层解析。 我们先把Document对象,和根元素获取出来

public class Dom4JTest1 {
    public static void main(String[] args) throws Exception {
        // 1、创建一个Dom4J框架提供的解析器对象
        SAXReader saxReader = new SAXReader();

        // 2、使用saxReader对象把需要解析的XML文件读成一个Document对象。
        Document document =
                saxReader.read("properties-xml-log-app\\src\\books.xml");

        // 3、从文档对象中解析XML文件的全部数据了
        Element root = document.getRootElement();
        System.out.println(root.getName());
    }
}







 



 




准备好的books.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<books>

	<book>
		<name>凡人修仙传</name>
		<author>亡语</author>
		<desc>讲述韩立修仙的故事</desc>
	</book>
	<book>
		<name>重生在黑马当班长</name>
		<author>嘿嘿</author>
		<desc>讲述在黑马学java的日子</desc>
	</book>
	
</books>

4️⃣ 2.4 XML文件写入 🚀 了解

能不能往XML文件中写入数据呢? 答案是可以的。

DOM4J也提供了往XML文件中写标签的方法,但是用起来比较麻烦不建议使用

我们自己使用StringBuilder按照标签的格式拼接,然后再使用BufferedWriter写到XML文件中去就可以了。

public class Dom4JTest2 {
    public static void main(String[] args) {
        // 1、使用一个StringBuilder对象来拼接XML格式的数据。
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n");
        sb.append("<book>\r\n");
        sb.append("\t<name>").append("从入门到跑路").append("</name>\r\n");
        sb.append("\t<author>").append("dlei").append("</author>\r\n");
        sb.append("\t<price>").append(999.99).append("</price>\r\n");
        sb.append("</book>");

        try (
                BufferedWriter bw = new BufferedWriter(new FileWriter("properties-xml-log-app/src/book.xml"));
                ){
            bw.write(sb.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

5️⃣ 2.5 XML约束 🚀了解

XML约束

XML约束指的是限制XML文件中的标签或者属性,只能按照规定的格式提高规范性

比如我在项目中,想约束一个XML文件中的标签只能写<书>、<书名>、<作者>、<售价>这几个标签,如果写其他标签就报错。

约束技术:

1668001422123
1668001422123

一种是DTD约束已淘汰、一种是Schame约束下面作业可以体验一下效果

作业练习

🚩 1. 按照下面的提示操作一下,体会一下约束的作用。

  • 第一步:在idea的src下创建一个pom.xml文件
  • 第二步:在pom.xml文件中,粘贴下列代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>javaproject</artifactId>
<groupId>com.huawei</groupId>
<version>1.0</version>
<modelVersion>4.0.0</modelVersion>

<!--   试着在这里写你自定一点标签 如<book>-->

</project>









 


  • 第三步:在上述代码深色地方,写一些自定义的标签,如:<book>,或者输入 <看是否有提示

三、日志技术 🚩 ✏️

1️⃣ 3.1 日志概述 🍐

工作场景

  • 系统系统能记住某些数据被谁操作,比如被谁删除了?
  • 想分析用户浏览系统的具体情况,比如挖掘用户的具体喜好
  • 当系统在开发中或者上线后出现了Bug,崩溃了,该通过什么去分析此时可以debug吗?,定位Bug?
点击查看解决方案

日志就可以帮我们解决以上的问题。

  • 日志就好比生活中的日记,日记可以记录生活中的点点滴滴;
  • 而程序中的日志,通常就是一个文件,里面记录了程序运行过程中产生的各种数据

日志技术有如下好处:

  1. 日志可以将系统执行的信息,方便的记录到指定位置,可以是控制台、可以是文件、可以是数据库电脑关机不会消失

  2. 日志可以随时以开关的形式控制启停非常灵活,无需侵入到源代码中去修改。

2️⃣ 3.2 日志的体系 🚀

日志框架和日志的体系

  1. 所谓日志框架就是由一些牛人或者第三方公司已经做好的实现代码,后来者就可以直接拿过去使用开发实用。🍐
  2. 日志框架有很多种,比如有JUL(java.util.logging)、Log4j、logback等。但是实现了同一套接口 一通百通 🍐

比较常用的日志框架,和日志接口的关系如下图所示 1668044513873

  1. Logback日志框架,在行业中最为广泛使用

Logback日志分为哪几个模块 🚀 1668044711404

3️⃣ 3.3 Logback快速入门

接下来,就带领小伙伴们快速使用一下Logback日志框架,

使用Logback记录几条日志信息 文件中去 和打印在控制台 上。

Logback入门案例

由于Logback是第三方提供的技术,所以首先需要啊将Jar包引入到项目中,具体步骤如下

  1. 在资料中找到slftj-api.jar、logback-core.jar、logback-classes.jar 这三个jar包,复制一下
  2. 在当前模块下面新建一个lib文件夹,把刚刚复制的三个jar包都粘贴到此处
  3. 从资料中找到logback.xml配置文件(也可以复制创建文件),将此文件复制粘贴到src目录下(必须是src目录)
点击查看logback.xml代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--输出流对象 默认 System.out 改为 System.err-->
        <target>System.out</target>
        <encoder>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
                %msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level]  %c [%thread] : %msg%n</pattern>
        </encoder>
    </appender>

    <!-- File是输出的方向通向文件的 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!--日志输出路径-->
        <file>D:/log/itheima-data.log</file>
        <!--指定日志文件拆分和压缩规则-->
        <rollingPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--通过指定压缩文件名称,来确定分割文件方式-->
            <fileNamePattern>D:/log/itheima-data-%i-%d{yyyy-MM-dd}-.log.gz</fileNamePattern>
            <!--文件拆分大小-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
    </appender>

    <!--
        1、控制日志的输出情况:如,开启日志,取消日志
    -->
    <root level="debug">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE" />
    </root>
</configuration>

课堂作业

🚩 按照上述步骤,完成课堂案例吧!

  1. 导入jar包(3个),导入xml配置文件,写一个类,书写一个main方法 快快开始把!!

运行main方法后,观察d盘的log目录下 是否存在日志文件

4️⃣ 3.4 日志配置文件 🚀

Logback提供了一个核心配置文件logback.xml,日志框架在记录日志时会读取配置文件中的配置信息,从而记录日志的形式。具体可以做哪些配置呢?

配置

  1. 可以配置日志输出的位置是文件、还是控制台
  2. 可以配置日志输出的格式
  3. 还可以配置日志关闭和开启、以及哪些日志输出哪些日志不输出。 实操一下
  • 如下图所示,控制日志往文件中输出,还是往控制台输出
1668045955362
1668045955362

5️⃣ 3.5 配置日志级别

1668046420402
1668046420402
  • 在哪里配置日志级别呢?如下图所示
1668046551345
1668046551345
  • Logback只输出大于或者等于核心配置文件配置的日志级别信息。小于配置级别的日志信息,不被记录。

相关信息

  • 配置的是trace,则trace、debug、info、warn、error级别的日志都被输出
  • 配置的是debug, 则debug、info、warn、error级别的日志被输出
  • 配置的是info,则info、warn、error级别的日志被输出

课堂作业

🚩 1. 思考一下,是否是级别越低越好,因为这样信息越多越全?

四、单元测试 🚩 ✏️

4.1 单元测试快速入门

所谓单元测试,就是针对最小的功能单元,编写测试代码对其进行正确性测试。

在main方法中写测试代码有如下的几个问题,如下图所示:

1668506399372
1668506399372

为了测试更加方便,有一些第三方的公司或者组织提供了很好用的测试框架,给开发者使用。这里给小伙伴们介绍一种Junit测试框架。

Junit是第三方公司开源出来的,用于对代码进行单元测试的工具(IDEA已经集成了junit框架)。相比于在main方法中测试有如下几个优点。👍

1668506713783
1668506713783

我们知道单元测试是什么之后,接下来带领小伙伴们使用一下。由于Junit是第三方提供的,所以我们需要把jar包导入到我们的项目中,才能使用,具体步骤如下图所示:

1668507051101
1668507051101

接下来,我们就按照上面的步骤,来使用一下.

点击查看验证代码

先准备一个类,假设写了一个StringUtil工具类,代码如下

public class StringUtil{
    public static void printNumber(String name){
        System.out.println("名字长度:"+name.length());
    }
}

接下来,写一个测试类,测试StringUtil工具类中的方法能否正常使用。

public class StringUtilTest{
    @Test
    public void testPrintNumber(){
        StringUtil.printNumber("admin");
        StringUtil.printNumber(null);
    }
}

写完代码之后,我们会发现测试方法左边,会有一个绿色的三角形按钮。点击这个按钮,就可以运行测试方法。

1668507501024
1668507501024

4.2 单元测试断言 🍐

接下来,我们学习一个单元测试的断言机制

所谓断言:意思是程序员可以预测程序的运行结果,检查程序的运行结果是否与预期一致。

点击查看代码

我们在StringUtil类中新增一个测试方法

 public static int getMaxIndex(String data){
     if(data == null){
         return -1;
     }
     return data.length();
 }

接下来,我们在StringUtilTest类中写一个测试方法

public class StringUtilTest{
    @Test
    public void testGetMaxIndex(){
       int index1 = StringUtil.getMaxIndex(null);
       System.out.println(index1);
        
       int index2 = StringUtil.getMaxIndex("admin");
       System.out.println(index2);
        
        //断言机制:预测index2的结果可能是4
        Assert.assertEquals("方法内部有Bug",4,index2);
    }
}










 


运行测试方法,结果如下图所示,表示我们预期值与实际值不一致

1668508226111
1668508226111

作业

🚩 1. 参考上面的单元测试的入门案例,练习一下吧?

  1. 测试方法test01 sout输出"我和java不得不说的故事" 观察运行颜色
  2. 测试方法test02 sout输出1/0 观察运行颜色
  3. 测试方法test03 使用断言 Assert.assertEquals("我估计有错",4,2); 观察运行后的颜色

4.3 Junit框架的常用注解 🚀 了解

小伙伴们,刚才我们以及学习了@Test注解,可以用来标记一个方法为测试方法,测试才能启动执行。

除了@Test注解,还有一些其他的注解,我们要知道其他注解标记的方法什么时候执行,以及其他注解在什么场景下可以使用。

1668508373865
1668508373865

接下来,我们演示一下其他注解的使用。我们在StringUtilTest测试类中,再新增几个测试方法。代码如下

public class StringUtilTest{
    @Before
    public void test1(){
        System.out.println("--> test1 Before 执行了");
    }
    @BeforeClass
    public static void test11(){
        System.out.println("--> test11 BeforeClass 执行了");
    }
    @After
    public void test2(){
        System.out.println("--> test2 After 执行了");
    }
    @AfterCalss
    public static void test22(){
        System.out.println("--> test22 AfterCalss 执行了");
    }
}

 



 



 



 




执行上面的测试类,结果如下图所示,观察执行结果特点如下

1.@BeforeClass标记的方法,执行在所有方法之前
2.@AfterCalss标记的方法,执行在所有方法之后
3.@Before标记的方法,执行在每一个@Test方法之前
4.@After标记的方法,执行在每一个@Test方法之后
1668508793279
1668508793279

应用场景

我们现在已经知道每一个注解的作用了,那他们有什么用呢?应用场景在哪里?

  1. 测试之前,需要加载准备数据
  2. 测试之后,需要释放内存

最后,我们再补充一点。前面的注解是基于Junit4版本的,再Junit5版本中对注解作了更新,但是作用是一样的换汤不换药。所以这里就不做演示了

1668509275659
1668509275659

五、多线程 🚩 ❤️ 🍐

多线程

接下来我们来学习一个全新而且非常重要的知识,叫做多线程

线程其实是程序中的一条执行路径

1668046984412
1668046984412

之前写过的程序,其实都是单线程程序,如上图代码,如果前面的for循环没有执行完,for循环下面的代码是不会执行的。

怎样的程序才是多线程程序呢?

  1. 如下图所示:
  • 12306网站就是支持多线程的,因为同时可以有很多人一起进入网站购票,而且每一个人互不影响。
  • 百度网盘,可以同时下载或者上传多个文件。
1668047091631
1668047091631
  • 单线程程序,就好比农村的路,同时只能一辆车通过,而4车道显然同时能通过多辆车

这些程序中其实就有多条执行路径,每一条执行执行路径就是一条线程,所以这样的程序就是多线程程序。

认识了什么是多线程程序,那如何使用Java创建线程呢? Java提供了几种创建线程的方式

多线程的创建方式 ✏️

1️⃣ 4.1 线程创建方式1

Java为开发者提供了一个类叫做Thread,此类的对象用来表示线程。创建线程并执行线程的步骤如下

1.定义一个子类继承Thread类,并重写run方法
2.创建Thread的子类对象
3.调用start方法启动线程(启动线程后,会自动执行run方法中的代码)

代码如下

public class MyThread extends Thread{
    // 2、必须重写Thread类的run方法
    @Override
    public void run() {
        // 描述线程的执行任务。
        for (int i = 1; i <= 5; i++) {
            System.out.println("子线程MyThread输出:" + i);
        }
    }
}

再定义一个测试类,在测试类中创建MyThread线程对象,并启动线程

public class ThreadTest1 {
    // main方法是由一条默认的主线程负责执行。
    public static void main(String[] args) {
        // 3、创建MyThread线程类的对象代表一个线程
        Thread t = new MyThread();
        // 4、启动线程(自动执行run方法的)
        t.start(); 

        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程main输出:" + i);
        }
    }
}

打印结果如下图所示,我们会发现MyThread和main线程在相互抢夺CPU的执行权(注意:哪一个线程先执行,哪一个线程后执行,目前我们是无法控制的,每次输出结果都会不一样

1668047848218
1668047848218

最后我们还需要注意一点:不能直接去调用run方法,如果直接调用run方法就不认为是一条线程启动了,而是把Thread当做一个普通对象,此时run方法中的执行的代码会成为主线程的一部分。此时执行结果是这样的。

1668048108548
1668048108548

作业

🚩 1. 利用上述的3种创建方式的任意1种创建线程,输出100次我爱java

提示:可以创建100个线程,每个线程打印1次,也可以创建1个线程,单个线程打印100次,还可以创建10个线程,每个线程打印10次

🚩 2.参考上述MyCallable案例,完成一个表白100次,然后给一个结果:你是个好人的案例

提示:在call中运行100次后才返回:你是好人

多线程常用方法 🚩

1668051403591
1668051403591

下面我们演示一下getName()setName(String name)currentThread()sleep(long time)这些方法的使用效果。

点击查看演示代码
public class MyThread extends Thread{
    public MyThread(String name){
        super(name); //1.执行父类Thread(String name)构造器,为当前线程设置名字了
    }
    @Override
    public void run() {
        //2.currentThread() 哪个线程执行它,它就会得到哪个线程对象。
        Thread t = Thread.currentThread();
        for (int i = 1; i <= 3; i++) {
            //3.getName() 获取线程名称
            System.out.println(t.getName() + "输出:" + i);
        }
    }
}

再测试类中,创建线程对象,并启动线程

public class ThreadTest1 {
    public static void main(String[] args) {
        Thread t1 = new MyThread();
        t1.setName(String name) //设置线程名称;
        t1.start();
        System.out.println(t1.getName());  //Thread-0

        Thread t2 = new MyThread("2号线程");
        // t2.setName("2号线程");
        t2.start();
        System.out.println(t2.getName()); // Thread-1

        // 主线程对象的名字
        // 哪个线程执行它,它就会得到哪个线程对象。
        Thread m = Thread.currentThread();
        m.setName("最牛的线程");
        System.out.println(m.getName()); // main

        for (int i = 1; i <= 5; i++) {
            System.out.println(m.getName() + "线程输出:" + i);
        }
    }
}

执行上面代码,效果如下图所示,我们发现每一条线程都有自己了名字了。

1668052028054
1668052028054
点击查看join方法代码
public class ThreadTest2 {
    public static void main(String[] args) throws Exception {
        // join方法作用:让当前调用这个方法的线程先执行完。
        Thread t1 = new MyThread("1号线程");
        t1.start();
        t1.join();

        Thread t2 = new MyThread("2号线程");
        t2.start();
        t2.join();

        Thread t3 = new MyThread("3号线程");
        t3.start();
        t3.join();
    }
}

执行效果是1号线程先执行完,再执行2号线程;2号线程执行完,再执行3号线程;3号线程执行完就结束了。

1668052307537
1668052307537

我们再尝试,把join()方法去掉,再看执行效果。此时你会发现2号线程没有执行完1号线程就执行了效果是多次运行才出现的,根据个人电脑而异,可能小伙伴半天也出现不了也是正常的

1668052414444
1668052414444

总结

  1. 重点练习cureentThread 获取当前线程
  2. 练习sleep(毫秒) 让当前执行的线程休眠,俗称睡一下

并发和并行 🍐

先学习第一个补充知识点,并发和并行。在讲解并发和并行的含义之前,我们先来了解一下什么是进程、线程?

  • 正常运行的程序(软件)就是一个独立的进程
  • 线程是属于进程,一个进程中包含多个线程
  • 进程中的线程其实并发和并行同时存在(继续往下看)

我们可以打开系统的任务管理器看看(快捷键:Ctrl+Shfit+Esc),自己的电脑上目前有哪些进程。

1668069176927
1668069176927

知道了什么是进程和线程之后,接着我们再来学习并发和并行的含义。

首先,来学习一下什么是并发?

进程中的线程由CPU负责调度执行,但是CPU同时处理线程的数量是优先的,为了保证全部线程都能执行到,CPU采用轮询机制为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。(简单记:并发就是多条线程交替执行)

接下,再来学习一下什么是并行?

并行指的是,多个线程同时被CPU调度执行。如下图所示,多个CPU核心在执行多条线程

1668069524799
1668069524799

最后一个问题,多线程到底是并发还是并行呢?

其实多个线程在我们的电脑上执行,并发和并行是同时存在的。

线程的生命周期 🍐

接下来,我们学习最后一个有关线程的知识点,叫做线程的生命周期。所谓生命周期就是线程从生到死的过程中间有哪些状态,以及这些状态之间是怎么切换的。

为了让大家同好的理解线程的生命周期,先用人的生命周期举个例子,人从生到死有下面的几个过程。在人的生命周期过程中,各种状态之间可能会有切换,线程也是一样的。

1668069740969
1668069740969

接下来就来学习线程的生命周期。在Thread类中有一个嵌套的枚举类叫Thread.Status,这里面定义了线程的6中状态。如下图所示

1668069923403
1668069923403
NEW: 新建状态,线程还没有启动
RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。

这几种状态之间切换关系如下图所示

1668070204768
1668070204768
点击查看演示代码

/**
 * 1.线程有6种状态,如果不记得,直接在Thread源码中,看State枚举
 * 2.wait方法会导致线程处于Waiting状态,直到被notify或者notifyAll唤醒, wait,notify,notifyAll必须在synchronized 包裹中执行
 * 3.当线程执行完后,会处于消亡状态
 */
public class ThreadStateTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

              synchronized (Thread.class){
//                  Thread.class.wait(); //思考:如果使用wait 此时状态是什么?
                  Thread.sleep(2222); //思考:如果使用sleep 此时状态是什么?

              }

                System.out.println(Thread.currentThread().getName()+" 执行了");
            }
        });



        System.out.println("1.此时thread的状态为:"+thread.getState());
        thread.start();
        System.out.println("2.此时thread的状态为:"+thread.getState());
        Thread.sleep(1111);
        System.out.println("3.此时thread的状态为:"+thread.getState());
        Thread.sleep(1111);
        System.out.println("4.此时thread的状态为:"+thread.getState());

    }
}



作业

来做点作业巩固一下吧!!!✏️ 💪

  • 建议也是要求:
    1. 做题之前先审题,标注可能用到的技术
    2. 做题要写注释,然后写代码
    3. 先问自己,再看笔记,实在不知道再问他人

  1. 页底标记 ↩︎