详细剖析多线程(更新中...)

news2024/11/15 21:50:14

文章目录

  • 前言
  • 一、认识线程
    • 1.1线程概念
    • 1.2为什么要有线程
    • 1.3线程和进程的区别(经典面试题)
  • 二、创建线程
    • 2.1继承 Thread 类,重写run
    • 2.2实现 Runnable 接口,重写run
    • 2.3继承 Thread 类,重写run,匿名内部类
    • 2.4实现 Runnable 接口,重写run,匿名内部类
    • 2.5lambda表达式【推荐写法】
    • 2.6sleep提前唤醒
  • 三、Thread的几个常见属性
  • 四、Thread类的基本使用
    • 4.1启动线程
      • start和run的区别?(经典面试题)
    • 4.2终止线程
      • 4.3等待线程
  • 五、线程的状态


前言

在当今科技发展迅速的社会中,多线程编程已经成为一种必不可少的技能。随着计算机硬件的发展,多核处理器已经成为主流,而多线程编程可以充分利用这些处理器的性能,提高程序的运行效率。因此,掌握多线程编程已经成为程序员们必须具备的技能之一。


一、认识线程

1.1线程概念

⼀个线程就是⼀个 “执⾏流”. 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 “同时” 执⾏着多份代码.

1.2为什么要有线程

  • 尽管多进程也能实现并发编程,但是线程比进程更轻量,线程的创建、调度、销毁都比进程更快。
  • "并发编程"成为刚需,大部分现代电脑使用的操作系统都支持多核CPU,这样可以更好地利用CPU资源,提高系统性能,而并发编程能更充分利用多核CPU。
  • 有些场景需要"等待IO",为了让等待IO的时间可以去做其他工作,也需要并发编程提高效率。

1.3线程和进程的区别(经典面试题)

1.进程包含线程。
2.线程是系统调度执行的基本单位:每个线程是一个独立的执行流,可以执行一些代码,并且单独的参与到CPU的调度中( 状态、上下文、优先级、记账信息…每个线程有自己的一份);进程是系统资源分配的基本单位:每个进程有自己的资源,进程中的线程共用这一份资源(主要是内存空间和文件描述符)([^1]: 文件描述符是一个用于标识已被进程打开的文件的整数。在Unix-like操作系统中,包括Linux和Mac OS等,文件描述符是对文件、管道、套接字等I/O资源的引用。每个进程都有一个文件描述符表,其中存储了该进程打开的文件的信息)。
3.进程和进程之间不会互相影响;同一个进程中的线程之间,可能会相互干扰,引起线程安全问题,例如同一个进程中的某个线程抛出异常,可能影响到其他线程,会把整个进程中的所有线程都异常终止。
4.线程并不是越多越好,适度就行,如果线程过多,调度开销可能非常明显。

二、创建线程

2.1继承 Thread 类,重写run

package thread;
//1.创建一个自己的类,继承这个Thread
//为什么这个Thread可以直接用不需要导包?因为java标准库中,有一个特殊的包java.long,默认导入的
//为什么类前面没有public?因为一个.java文件中只能有一个public类
//这个类如果没有public包级作用于,就是只能在当前包里被其他的类使用
class MyThread extends Thread {
    @Override
    public void run() {
        // run 方法就是该线程的入口方法.
        System.out.println("hello world");
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        // 2. 根据刚才的类, 创建出示例. (线程实例, 才是真正的线程).
        // MyThread t = new MyThread();
        Thread t = new MyThread();
        // 3. 调用 Thread 的 start 方法, 才会真正调用系统 api, 在系统内核中创建出线程.
        t.start();
    }
}

2.2实现 Runnable 接口,重写run

package Thread;

//实现Runnable接口,重写run

class MyThread3 implements Runnable{
//Runnable可以理解为“可执行的”,通过这个接口,可以抽象表示出一段可以被其他实体来执行的代码
    @Override
    public void run() {
        while(true){
            System.out.println("hello runnable");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

public class ThreadDemo3 {
    public static void main(String[] args) throws InterruptedException {
        /*Runnable runnable=new MyThread3();
        Thread t=new MyThread(runnable);*/
        //还是需要搭配Thread类才能真正在系统中创建出线程
        //这种写法其实就是把线程和要执行的任务进行了解耦合了
        Thread t=new Thread(new MyThread3());
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

2.3继承 Thread 类,重写run,匿名内部类

package Thread;

//继承Tread重写run,但是要使用匿名内部类(在一个类里面定义一个类,匿名意味着没有名字,不能重复使用)
public class ThreadDemo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){//写{}意思是要定义一个类,与此同时,这个行新的类
            //继承自Tread,此处{}中可以定义子类的属性和方法,此处最主要的目的是重写run方法
            //与此同时,这个代码还创建了子例的实例
            //t指向的实例(对象)并非单纯的Thread,而是Thread的子类(因为他是匿名类,所以我们也不知道他叫啥)

            @Override
            public void run() {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

2.4实现 Runnable 接口,重写run,匿名内部类

package Thread;
//实现Runnable,重写run,匿名内部类
public class TreadDemo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(new Runnable(){//实现Runnable,重写run,匿名内部类
            @Override
            public void run() {
                while(true){
                    System.out.println("hello runnable");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });//Thread构造方法的参数,填写Runnable的匿名内部类的实例
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

2.5lambda表达式【推荐写法】

package Thread;

//常用并推荐的写法,使用lambda表达式(匿名函数/方法)
//这个写法相当于实现了Runnable重写run,lambda代替了Runnable的位置
public class ThreadDemo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{ //这里的()是形参列表,这里能带参数,线程入口不需要参数
                                  //()前面应该有一个函数名,此处作为匿名函数,就没有名字
            while(true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

2.6sleep提前唤醒

在这里插入图片描述
我们在写代码的时候可以发现,sleep这行代码会报错,报错信息是受查异常。
那么什么是受查异常呢?
代码会受查异常是因为在编译时,编译器要求程序必须处理可能抛出的异常,否则会报编译错误。受查异常是指在代码中明确定义了可能发生的异常,并且在方法中必须使用try-catch或者throws关键字来处理这些异常,否则会导致编译错误。
这里的异常意味着sleep(1000)过程中,可能被提前唤醒,正常情况下会休眠满一秒才能醒过来继续往下执行,也会有特殊情况提前唤醒。
另外还有非受查异常,非受查异常是指在代码中没有明确定义,或者不受编译器检查的异常,通常是由程序逻辑错误或者环境问题导致的异常,例如空指针异常、数组越界异常等。在处理非受查异常时,可以选择捕获处理,也可以直接抛出给调用方处理。非受查异常不会导致编译错误,但是如果没有处理可能会导致程序运行时异常。
在处理sleep提前唤醒可能导致的问题时,需要谨慎设计程序逻辑,确保在提前唤醒时程序的状态和数据都是正确的。同时,需要保证程序在提前唤醒后能够正确地恢复执行。

三、Thread的几个常见属性

属性获取方法
IDgetId():jvm自动分配的身份标识,保证唯一性,不同线程不会重复
名称getName():名称是各种调试⼯具⽤到
状态getState():状态表⽰线程当前所处的⼀个情况
优先级getPriority():在java中设置优先级效果不是很明显(对内核调度器的调度过程产生了一些影响)由于系统的随机调度
是否后台线程isDaemon():也可以称为是否是"后台线程",关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏
是否存活isAlive():简单的理解,为 run ⽅法是否运⾏结束了
是否被中断isInterrupted()

以下是一些代码示例:

package Thread;
//获取线程引用

class MyThread4 extends Thread{
    //如果是继承Thread,直接使用this拿到线程实例
    //如果是Runnable或者lambda的方式,就不可以,因为此时的this已经不再指向Thread对象了
    //就只能使用Thread.currentThread()了
    @Override
    public void run() {
        System.out.println(this.getId()+","+this.getName());
    }
}
public class ThreadDemo13 {
    public static void main(String[] args) throws InterruptedException {
        MyThread4 t1=new MyThread4();
        MyThread4 t2=new MyThread4();
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println(t1.getId()+","+t1.getName());//与run方法里面执行结果是一样的
        System.out.println(t2.getId()+","+t2.getName());
    }
}
package Thread;
//获取线程引用
//Thread.currentThread()获取到当前线程的引用

public class ThreadDemo14 {
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            Thread t=Thread.currentThread();
            System.out.println(t.getName());
        });
        Thread t2=new Thread(()->{
            Thread t=Thread.currentThread();
            System.out.println(t.getName());
        });
        t1.start();
        t2.start();
    }
}

package Thread;
//前台线程的运行会阻止进程结束
//后台线程的运行不会阻止进程结束
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        },"这是我的线程");
        //在start之前设置线程setDaemon为true为后台线程,进程直接结束(如果之后设置来不及)
        t.setDaemon(true);
        t.start();
    }
}

package Thread;

public class TreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println("start之前:"+t.isAlive());
        t.start();
        System.out.println("start之后:"+t.isAlive());
        Thread.sleep(2000);//如果把2000改成1000,此时由于多线程调度,isAlive就未知了
        //2s之后线程t结束
        System.out.println("t结束之后:"+t.isAlive());
    }
}
//java代码中定义的线程对象实例,虽然表示一个线程,这个对象本身的生命周期和内核中的pcb生命周期是不完全一样的
//Thread t=new Thread(),此时t对象有了,但是内核pcb还没有,isAlive就是false
//t.start(),真正在内核中创建出这个pcb,此时isAlive就是true
//当线程run执行完了,此时内核中线程就结束了,内核pcb释放,但是此时变量t可能还存在,于是isAlive也是false

四、Thread类的基本使用

4.1启动线程

之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象,但线程对象被创建出来并不意味着线程就开始运⾏了。调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程.

start和run的区别?(经典面试题)

start和run都是线程的启动方法,但是它们之间有一些区别:

  1. start方法是启动一个新的线程,当调用start方法时,会为该线程分配一个新的栈空间,并且在线程启动后会自动调用run方法来执行线程的任务。而直接调用run方法是在当前线程中执行run方法的任务。
  2. 使用start方法来启动线程能够实现并发执行,因为每次调用start方法都会创建一个新的线程。而直接调用run方法则只是在当前线程中执行任务,无法实现并发执行。

总的来说,start方法用于启动一个新的线程来执行任务,实现并发执行,需要较多的系统资源开销。而run方法则可以在当前线程中执行任务,但无法实现并发执行,不需要额外的系统资源开销。

4.2终止线程

eg.下面这个例子在执行sleep的过程中,调用interrupt,大概率sleep休眠时间还没到就被提前唤醒,当我们去掉代码中的sleep时,能发现interrupt可以让线程顺利结束的,

public class ThreadDemo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {//判定是否结束
                System.out.println("我是一个线程, 正在工作中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程执行完毕!");
        });

        t.start();

        Thread.sleep(3000);
        // 使用一个 interrupt 方法, 来修改刚才标志位的值,设置让线程结束
        System.out.println("让 t 线程结束");
        t.interrupt();
    } } 
    ```

在这里插入图片描述

提前唤醒会出现两件事–

  • 抛出InterruptedException异常,这个异常紧接着就会被catch获取到
  • 清除Thread对象的isInterrupted标志位,意思是通过interrupt方法,已经把标志位设为true了,但是sleep提前唤醒操作,就把标志位又设回false了,此时循环还是会继续执行
    那么怎么让线程结束呢? 在catch里面加上break就可以了~
    在这里插入图片描述
    线程顺利结束~
    在这里插入图片描述
    根据上诉,为什么sleep要清空标志位呢? 是为了给程序员更多的“可操作性空间”,可以在catch语句中写一些代码做一些处理——
    1)让线程立刻结束(加上break)
    2)让线程不结束,继续执行(不加break)
    3)让线程执行一些逻辑后再结束(写一些其他代码再break)

另外,在实际开发中,catch里面应该写什么样的代码?(如果程序出现异常怎样处理更合理)
①尝试自动恢复,能自动恢复的尽量自动恢复,比如出现了一个网络通信相关的异常可以在catch尝试重连网络。
②记录日志(异常信息记录到文件中)。 ③发出警报,针对比较严重的问题,包括但不限于给程序员发邮件,发短信,打电话…
④也有少数的正常的业务逻辑,会依赖到catch,比如文件操作中的方法,就要通过catch来结束循环之类…[非常规用法]

4.3等待线程

join()方法让一个线程等待另一个线程结束。线程之间的执行顺序,是无序的调度过程,我们无法指定哪个线程何时结束,有的时候又希望能够控制线程之间结束的先后顺序,此时就可以用join,join这个方法会让等待线程阻塞,一直阻塞等待到被等待的线程执行完run方法。

在这里插入图片描述
执行join的时候,看t线程是否在运行
如果t运行中,main线程会阻塞(main线程暂时不参与cpu执行)
如果t运行结束,main线程从阻塞中恢复,并且继续往下执行

package Thread;

import javax.print.attribute.standard.PresentationDirection;

public class ThreadDemo12 {
    // t 线程把计算的结果放到 result 中.
    private static long result = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            long tmp = 0;
            for (long i = 1; i <= 50_0000_0000L; i++) {//实际上计算结果太大会溢出这里先忽略这个事情
                tmp += i;
            }
            result += tmp;
        });
        Thread t2 = new Thread(() -> {
//            try {
//                // 如果把 join 加到末尾, 这个时候, 就还是 t 和 t2 并发执行, 没啥区别
//                // 如果把 join 加到开头, 这个时候, 就是先执行 t, t2 先阻塞. 等到 t 执行完了之后, t2 继续执行. 又成了串行执行了.
//                t.join();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }

            long tmp = 0;
            for (long i = 50_0000_0001L; i <= 100_0000_0000L; i++) {
                tmp += i;
            }
            result += tmp;

        });
        long beg = System.currentTimeMillis();
        t.start();
        t2.start();

        // 主要就是不知道 t 线程要执行多久
        // Thread.sleep(1000);

        // 使用 join, 就会严格按照 t 线程执行结束来作为等待的条件.
        // 什么时候 t 运行结束(计算完毕), 什么时候, join 就结束等待
        // t 运行 1ms, join 就等待 1ms; t 运行 10s, join 就等待 10s
        // 确保 join 之后得到的结果, 一定是靠谱的结果.
        t.join();
        t2.join();
        long end = System.currentTimeMillis();

        // 上面加上 join 之后, 结果就一定是 t 线程执行结束的结果了.
        System.out.println("result = " + result);
        System.out.println("time = " + (end - beg) + " ms");
    }
}

五、线程的状态

• NEW: Thread对象创建好了,但是还没有调用start方法在系统中创建线程。
• RUNNABLE: 就绪状态,表示这个线程正在cpu上执行,或者准备就绪随时可以去cpu执行。
• TIMED_WAITING: 指定时间的阻塞,在达到一定时间之后自动解除阻塞。
• WAITING: 不带时间的阻塞(死等),必须满足一定的条件才会解除阻塞。
• TERMINATED: Thread对象仍存在,但是系统内部的线程已经执行完毕了。
• BLOCKED: 由于锁竞争引起的阻塞。

在这里插入图片描述


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

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

相关文章

WEB 表单练习题

任务如图&#xff1a; <html><head><meta charest"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head><body><table width"…

解决nginx报错nginx: [emerg] unknown log format main in 的方法

目录 一、故障描述1&#xff1a; 重启nginx是出现了如下错误 解决办法 二、故障描述2&#xff1a; 解决办法&#xff1a; 三、nginx介绍​ 四、nginx原理 五、nginx.conf配置文件 六、nginx负载均衡 七、正向代理、反向代理 一、故障描述1&#xff1a; 在添加Nginx的…

【WEEK4】 【DAY3】整合SSM框架之功能实现—修改、删除数据【中文版】

2024.3.20 Wednesday 接上文【WEEK4】 【DAY2】整合SSM框架之功能实现—总览、添加数据【中文版】 目录 7.6.修改功能7.6.1.修改BookController.java7.6.2.修改allBook.jsp7.6.3.新建updateBook.jsp7.6.4.修改MyBatis-config.xml7.6.5.运行 7.7.删除功能7.7.1.修改BookContro…

网络原理(4)——TCP协议的特性

目录 一、滑动窗口 1、ack丢了 2、数据丢了 二、流量控制&#xff08;流控&#xff09; 三、拥塞控制 拥塞窗口动态变化的规则 四、延时应答 五、捎带应答 六、面向字节流 七、异常情况 &#xff08;1&#xff09;进程崩溃了 &#xff08;2&#xff09;其中一方关机…

Ubuntu系统提示“/dev/mmcblk0p1 分区满了‘以及磁盘空间不够的处理办法

查看boot分区使用空间&#xff1a; df 查看已安装的内核版本&#xff1a; dpkg --get-selections | grep linux &#xff08;其中带image的一般就是旧版本&#xff0c;deinstall代表已经删除的旧版本&#xff0c;install代表还未删除的旧版本内核&#xff09; 查看系统当前…

基于java+springboot+vue实现的健身房管理系统(文末源码+Lw+ppt)23-523

摘 要 健身房管理的以往工作流程繁杂、多样、管理复杂与设备维护繁琐。而如今计算机已完全能够胜任健身房管理工作&#xff0c;而且更加准确、方便、快捷、高效、清晰、透明&#xff0c;它完全可以克服以上所述的不足之处。这将给查询信息和管理带来很大的方便&#xff0c;从…

晶体管测试仪系统能测 IGBT. Mosfet. Diode. BJT......

晶体管测试仪系统能测试很多电子元器件的静态直流参数&#xff08;如击穿电压V(BR)CES/V(BR)DSs、漏电流ICEs/lGEs/IGSs/lDSs、阈值电压/VGE(th)、开启电压/VCE(on)、跨导/Gfe/Gfs、压降/Vf、导通内阻Rds(on)&#xff09;。 测试种类覆盖 7 大类别26分类&#xff0c;包括“二极…

Redis 更新开源许可证 - 不再支持云供应商提供商业化的 Redis

原文&#xff1a;Rowan Trollope - 2024.03.20 未来的 Redis 版本将继续在 RSALv2 和 SSPLv1 双许可证下提供源代码的免费和宽松使用&#xff1b;这些版本将整合先前仅在 Redis Stack 中可用的高级数据类型和处理引擎。 从今天开始&#xff0c;所有未来的 Redis 版本都将以开…

力扣454. 四数相加 II

思路&#xff1a;把四个数组拆成两对&#xff0c;两个分别相加&#xff0c;记录第一对的相加结果进map里&#xff0c;再把第二对数组 0-nums2-nums4 去map里面找出现了几次&#xff0c;这题不用对重复的四元组去重&#xff0c;所以出现多次都有效。 class Solution {public int…

Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证

昨日&#xff0c;Redis 官方宣布了一项重要变更&#xff1a;他们将修改开源协议&#xff0c;未来所有版本将采用 “源代码可用” 的许可证。 具体来说&#xff0c;Redis 不再使用 BSD 3-Clause 开源协议进行分发。从 Redis 7.4 版本开始&#xff0c;Redis 将采用 SSPLv1 和 RSA…

探索大广赛获奖作品:职场设计风云的精彩篇章

2024第16届全国大学生创意广告大赛正在火热进行中&#xff0c;今天就与大家分享一些平面类往届优秀获奖作品&#xff0c;仅供参考~灵感慢慢&#xff0c;不要错过哦&#xff01; 大广赛命题素材下载&#xff1a; 下载地址https://js.design/f/Jspbti?sourcecsdn&planbtts…

Go语言学习Day1:什么是Go?

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1、走近Go①Go语言的Logo②Go语言的创始人③Go语…

excel文件可以转成word文件吗?汇帮PDF转换器帮你实现excel转word

将Excel文件转换为Word文档是一个相对简单的任务&#xff0c;但在执行过程中需要注意一些细节&#xff0c;以确保转换后的文档格式正确、内容清晰。下面将详细介绍用汇帮PDF转换器将Excel转Word的步骤和注意事项。 一、Excel文件准备 在进行转换之前&#xff0c;首先确保Excel…

软考高级:UML 图-状态图概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

科研学习|论文解读——通过调查实验了解公民对政府财政信息的认知(GIQ,2022)

原文题目 Understanding citizens perception of government fiscal information through a survey experiment 摘要 州和地方政府定期向公众披露财务信息&#xff0c;以便公开承认政府当前的财务状况。此类披露的目的是实现问责制并让公民了解政府的财务决策。(背景&#xff0…

MySQL之索引与事务

一 索引的概念 一种帮助系统查找信息的数据 数据库索引 是一个排序的列表&#xff0c;存储着索引值和这个值所对应的物理地址无须对整个表进行扫描&#xff0c;通过物理地 址就可以找到所需数据是表中一列或者若干列值排序的方法 需要额外的磁盘空间 索引的作用 1 数据库…

mysqly索引(explain 执行计划)

关键词 执行计划 EXPLAIN 语句查看mysql 优化后的语句 show warnings;EXPLAIN 执行后&#xff0c;各列的含义 要点&#xff1a; select_type 如何查询 表type 如何查询 行key 如何使用 索引key_len 索引 使用多少rows 行 预计使用多少extra 表 的额外信息 1.id id列的编…

Moonsec_Projet_7

Moonsec_Projet_7 1、基础介绍 1.1 网络拓扑图 主机账号密码网卡IPWindows Server 2016administrator!#QWE123vmnet17(web) | vmnet18192.168.0.144(www.moonlab.com) | 10.10.1.131Windows Server 2012administrator!#QWEasdvmnet18 | vmnet1910.10.1.130 | 10.10.10.166Win…

笔记本电脑开机黑屏没反应怎么办?教你逐一排查问题

笔记本电脑是我们日常工作和学习中不可或缺的工具&#xff0c;但有时候在开机时可能会遇到黑屏无反应的问题&#xff0c;这给我们的使用带来了困扰。然而&#xff0c;不用担心&#xff0c;通常情况下&#xff0c;这种问题是可以解决的。在本文中&#xff0c;我们将介绍三种常见…

Redis入门到入坑(一)

Redis入门到入坑&#xff08;一&#xff09; Redis缓存入门简介Redis初始操作Redis数据存储操作 Redis常用数据类型简介String类型操作实践Hash类型应用实践List类型应用实践Set类型应用实践 Java中操作redis准备工作Jedis的应用快速入门实现RedisTemplate应用项目工程实践 Red…