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

【多线程】初步认识Thread类及其应用

💐个人主页:初晴~

📚相关专栏:多线程 / javaEE初阶


        上篇文章我们简单介绍了什么是进程与线程,以及他们之间的区别与联系,实际应用中还是以多线程编程为主的,所以这篇文章就让我们更加深入地去剖析多线程编程的具体应用吧

目录

一、初识Thread类

1、创建线程

(1)继承Thread类

(2)实现Runnable接口

(3)匿名内部类

2、多线程的优势-增加运⾏速度

二、 Thread 类及常⻅⽅法

1、 Thread 的常⻅构造⽅法

2、 Thread 的⼏个常⻅属性

三、线程的状态

四、线程的核心操作

1、启动一个线程-start()

2、获取当前线程引用

3、休眠当前线程

4、线程的中断(终止)

5、线程等待-join()

总结


一、初识Thread类

⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏着多份代码.
Java 的线程 和 操作系统线程 的关系
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对⽤⼾层提供了⼀些 API 供⽤⼾使⽤(例如 Linux 的 pthread 库).

1、创建线程

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进⾏了进⼀步的抽象和封装
接下来我们就来看一下创建线程的几种写法吧:

(1)继承Thread类

编写的MyThread需要继承Thread类,不需要导包,因为Thread类是java.lang中内置的类。

继承不是主要目的,主要是为了重写Thread类中的run方法,在其中写入所创建线程需要执行的逻辑语句

若要让线程运行,需先实例化编写的MyThread类,接着调用start方法就会在进程内部创建一个新的线程,新的线程就会执行刚才run里的代码。

具体代码如下:

class MyThread extends Thread{//重写run方法@Overridepublic void run(){//线程执行的逻辑System.out.println("Hello World!");}
}
public class Main {public static void main(String[] args) {MyThread myThread=new MyThread();//创建线程myThread.start();}
}

这个代码,运行起来是一个进程,但这个进程包含了两个线程。
1、调用main方法的线程被称为“主线程”,之前提过一个进程中至少有一个线程,这个线程就是主线程
2、调用myThread.start()方法时会手动创建一个新的线程
主线程和新线程会并发/并行地在CPU上运行
不过,上述代码还不能很好地体现多线程编程的并发性与随机性,接下来用一个更加形象的代码表示一下:
class MyThread extends Thread{//重写run方法@Overridepublic void run(){while (true){System.out.println("Hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
public class Main {public static void main(String[] args) {MyThread t=new MyThread();//创建线程t.start();while (true){System.out.println("Hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

可以发现,多个线程之间,谁先去CPU上调度执行,这个过程是 “不确定的”,这个调度顺序取决于操作系统内核里的 “调度器”,调度器里有一套规则,但是对于应用程序开发,无法进行干预,也无法察觉,因此把这个过程近似于 “随机”,多线程的运行调度也被称之为 “抢占式执行”

注意:

上述代码中,并没有直接手动调用run方法,但是也被执行了。像run这种,用户手动定义了,但是没有手动调用,最终被系统/库/框架调用执行了的方法,被称为“回调函数(call back)”

(2)实现Runnable接口

Runnable就是用来描述“要执行的任务”是什么
class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println("Hello Runnable!");}
}
public class Main {public static void main(String[] args) {MyRunnable runnable=new MyRunnable();Thread myThread=new Thread(runnable);//创建线程myThread.start();}
}

通过Thread创建线程,而线程要执行的任务是通过Runnable来描述的,而不是通过Tread自己来描述,这样能起到一定的“解耦合”的作用,便于代码后期维护。

Runnable只是描述了一个任务,并不与“线程”强相关,后续执行这个任务的载体可以是线程,也可以是其他东西,比如线程池、虚拟线程(协程)等,一定程度上提高了代码的复用率。

对⽐上⾯两种⽅法:
继承 Thread 类, 直接使⽤ this 就表⽰当前线程对象的引⽤.
实现 Runnable 接⼝, this 表⽰的是 MyRunnable 的引⽤. 需要使⽤Thread.currentThread()

(3)匿名内部类

匿名指没有类名,内部类指定义在其它类内部的类,匿名内部类一般就是“一次性”使用的类,用完就丢掉,相对来说内聚性会更好一些

匿名内部类创建 Thread ⼦类对象
// 使⽤匿名类创建 Thread ⼦类对象
Thread t=new Thread(){@Overridepublic void run() {System.out.println("使⽤匿名类创建 Thread ⼦类对象");}
};
这个方法本质上和(1)是一致的,具体原理如下:
1、定义匿名内部类,这个类是Thread的子类
2、类的内部,重写了父类的run方法
3、创建了一个子类的实例,并把实例的引用赋值给了t
匿名内部类创建 Runnable ⼦类对象:
//使⽤匿名类创建 Runnable ⼦类对象
Thread t2=new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使⽤匿名类创建 Runnable ⼦类对象");}
});

lambda表达式创建 Runnable 子类对象:

Thread t3=new Thread(()-> System.out.println("使⽤lambda表达式创建 Runnable ⼦类对象"));
Thread t4=new Thread(()->{System.out.println("使用lambda表达式创建 Runnable 子类对象");
});

lambda表达式本质上就是匿名内部类的更简化的写法,很多时候,写匿名内部类都不是为了写“类”,而是写类中的“方法”,而lambda就可以避开类而直接描述其中的run方法


2、多线程的优势

可以观察多线程在⼀些场合下是可以提⾼程序的整体运⾏效率的。
使⽤ System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
serial 串⾏的完成⼀系列运算. concurrency 使⽤两个线程并⾏的完成同样的运算

public class ThreadAdvantage {// 多线程并不⼀定就能提⾼速度,可以观察,count 不同,实际的运⾏效果也是不同的private static final long count = 10_0000_0000;public static void main(String[] args) throws InterruptedException {// 使⽤并发⽅式concurrency();// 使⽤串⾏⽅式serial();}private static void concurrency() throws InterruptedException {long begin = System.nanoTime();// 利⽤⼀个线程计算 a 的值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (long i = 0; i < count; i++) {a--;}}});thread.start();// 主线程内计算 b 的值int b = 0;for (long i = 0; i < count; i++) {b--;}// 等待 thread 线程运⾏结束thread.join();// 统计耗时long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("并发: %f 毫秒%n", ms);}private static void serial() {// 全部在主线程内计算 a、b 的值long begin = System.nanoTime();int a = 0;for (long i = 0; i < count; i++) {a--;}int b = 0;for (long i = 0; i < count; i++) {b--;}long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("串⾏: %f 毫秒%n", ms);}
}

二、 Thread 类及常⻅⽅法

Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。
⽤我们上⾯的例⼦来看,每个执⾏流,也需要有⼀个对象来描述,类似下图所⽰,⽽ Thread 类的对象就是⽤来描述⼀个线程执⾏流的,JVM 会将这些 Thread 对象组织起来,⽤于线程调度,线程管理。

1、 Thread 的常⻅构造⽅法

代码事例:
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

如果不给线程起名字,那么默认就会是叫做Thread-0,Thread-1……

给线程起名字,并不会影响线程的执行效果,不过起一个合适的名字,更有利于调试程序

ThreadGroup线程组是把多个线程放在一组里,方便统一地设置线程的一些属性。不过现在很少会使用线程组了,线程相关属性用到也不多,更多的是会使用“线程池”

2、 Thread 的⼏个常⻅属性

ID 是JVM自动分配的,是线程的唯⼀标识,不同线程不会重复
名称是各种调试⼯具⽤的,Thread对象的身份标识
状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明
优先级⾼的线程理论上来说更容易被调度到
关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
是否存活,即简单的理解,为 run ⽅法是否运⾏结束了
线程的中断问题,下⾯我们进⼀步说明

关于前台线程与后台线程:

后台线程:如果这个线程执行过程中,不能阻止进程的结束,(虽然线程在执行着,但是进程要结束了,此时这个线程也会随之被带走),这样的线程就被称为“后台线程”,也被称为“守护线程”。

前台线程:如果某个线程在执行过程中,能够阻止进程结束,就被称为“前台线程”

一个线程中可以有多个前台线程(创建的线程默认是前台线程),必须所有的前台线程都结束,进程才能结束

关于线程是否存活:

isAlive()返回值为true表示还存活,false表示线程没了

代码中,new Thread对象的生命周期与内核中实际线程的生命周期不一定是一致的,可能会出现Thread对象存在但是内核中线程不存在的情况,如:

(1)调用start前,此时内核中还未创建线程

(2)线程中的run执行完毕,内核的线程销毁,但此时Thread对象任然存在

注意:不存在Thread对象不存在,而线程还存在的情况


三、线程的状态

线程的状态是⼀个枚举类型 Thread.State,反映了线程生命周期的不同阶段,了解这些更有利于对线程的调试与优化,以下是线程的六种主要状态:
NEW(初始):
  • Thread对象已创建,但是内核的线程还没有(还未调用start方法
  • 安排了⼯作, 还未开始⾏动
RUNNABLE(可运行):
  • 线程的start方法一旦被调用就会进入RUNNABLE状态
  • 就绪状态,可能正在CPU上运行,也可能在等待CPU分配资源以便运行
BLOCKED(阻塞):
  • 因为锁竞争而引起的阻塞等待的状态
  • 当线程试图获取某一对象的锁,而该对象的锁正被其它线程占有时,就会进入BLOCKED状态
  • 一般指线程因同步操作(synchronized)而被阻塞的状态
WAITING(等待):
  • 当线程调用了Object.wait(),Thread.join(),LockSupport.park()等方法时,就会进入WAITING状态
  • 此时线程不会争夺CPU资源,一直等待直到其它线程发出对应的通知信息(如notify()或notifyAll())时重新恢复RUNNABLE状态
  • 没有超时时间的阻塞等待,如果没有收到通知将会一直等待下去
TIMED_WAITING(超时等待):
  • 与WAITING状态类似,但是是有超时时间的等待
  • 当调用Object.wait(long timeout),Thread.join(long),Thread.sleep(long millis),LockSupport.parkNanos(),LockSupport.parkUntil()等方法时,线程会进入TIMED_WAITING状态
  • 线程将会等待直到被唤醒或超时
TERMINATED(终止):
  • 当线程的run方法执行完毕或者由于某些原因(如抛出为捕获的异常)而提前结束时,线程进入TERMINATED状态
  • 当前Thread对象虽然还在,但是内核的线程已经被销毁了(线程已经结束了)
  • 终止的线程无法被重启

可以形象的类比于以下状态:


上述线程状态可以通过jdk自带的jconsole来观察:

学习线程的状态,主要就是为了调试与优化,比如遇到某个线程没有正常运行时,就可以观察对应线程的状态,来确定是否是由于一些原因导致线程进入了阻塞状态


四、线程的核心操作

1、启动一个线程-start()

之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象,但线程对象被创建出来并不意味着线程就开始运⾏了。
覆写 run ⽅法是提供给线程要做的事情的指令清单
线程对象可以认为是把 李四、王五叫过来了
⽽调⽤ start() ⽅法,就是喊⼀声:”⾏动起来!“,线程才真正独⽴去执⾏了。

调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程
注意要区分好 runstart的关系:
run:线程的入口,描述了线程 要执行的任务
start:调用了 系统api,在系统内核中 真正创建了线程(创建PCB加入到链表中)
也就是说,如果直接调用run方法,虽然也能执行run方法内的程序,但也只会在调用它的线程中执行,是不会创建新的线程的,这样就背离多线程编程的初心了
注意
start对于一个Thread对象只能调用一次,java中的Thread对象与内核中的线程是“一对一”的关系,因此,不存在一个线程终止后,再通过调用start重新执行的情况
这是由于一个Thread对象能够对应到多个线程的话,管理起来就会非常麻烦,JVM的设计实现就会非常复杂了。因此java中希望一个Thread对象只能对应到系统中的一个线程,这样在调用start后就可以通过线程的状态来判断是否能成功创建。
如果一个Thread对象是没有调用过start的,此时就会处于NEW状态,此时调用start就可以顺利地执行创建出线程了。而如果已经调用过start,线程就会进入其它状态,只要不是处于NEW状态,接下来执行start都会抛出异常了

2、获取当前线程引用

想在某个线程中,获取到自身的Thread对象的引用,就可以通过currentThread()来获取

任何线程都可以通过这样的操作获取线程的引用

3、休眠当前线程

也是我们⽐较熟悉⼀组⽅法,有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的

public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}

线程执行sleep,就会使这个线程不参与CPU调度,从而把CPU资源让出来,给别人使用。像sleep这样的操作也被称之为“放权”,放弃使用CPU的权利

在有些开发场景中,如果某个线程CPU占用率过高,就可以通过sleep来改善。虽然线程的优先级就可以对其产生影响,但影响是比较有限的,通过sleep可以更加明显地影响到CPU占用

4、线程的中断(终止)

在工作中,我们可能会因为领导的一通电话,而不得不停下手头的工作,去做别的事。线程运行时可能也会遇到类似问题,有时我们可能会因为某些原因而需要提前结束线程的运行,该如何停止呢,这就涉及到停止线程的方式了。

如果有两个线程a和b,b正在运行,a想要b结束运行,其实核心就是a要想办法让b的run方法更快地执行完毕,此时b自然就结束了。而不是说b的run执行一半,a直接强行把b结束了。java中结束线程的方法是比较“温柔”的,并不是直接粗暴的。因为如果强制结束某个线程的话,可能导致其逻辑未完全执行,对应的结果数据是个“半成品”,从而影响程序最终的结果,这样肯定是不合理的。

1、一个简单的做法是使用自定义的标志位

public class Demo {//设置全局变量isQuit作为标志位public static boolean isQuit=false;public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{while (!isQuit){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();Thread.sleep(4000);System.out.println("main线程尝试终止 t 线程");//通过修改isQuit变量的值,就能终止 t 线程的执行了isQuit=true;}}

注意,如果把isQuit变成main方法的局部变量,就会出现以下情况:

我们可以看到lambda语句中的isQuit标红报错了。这与lambda表达式/匿名内部类的变量捕获操作有关。

isQuit与lambda表达式定义在一个作用域内,lambda的内部是可以访问到lambda外部(与lambda同一作用域)的变量的。但是lambda的变量是有要求的,能够捕获的变量得是final或者事实final(即虽没有final修饰,但并没有人修改),而上述代码中的isQuit变量被修改过,不是final/事实final,导致lambda表达式无法通过变量捕获操作获取到它,从而导致程序出现了错误

而当把isQuit写成成员变量后,就成了内部类访问外部类的成员变量,这本就是合法的,因此就不会出现问题了


2、使用Thread自带的interrupted作为标志位

 

Thread中有一个boolean类型的成员变量interrupted,它的初始值为false,表示未被终止,一旦其它线程调用了interrupt方法,就会设置上述标志位值为true,表示已被终止。

public class Main {public static void main(String[] args) throws InterruptedException {//下列的 lambda 的代码在编译器眼里,出现在 Thread t 的上方//此时 t 还没有被定义Thread t=new Thread(()->{//先获取到线程的引用Thread currentThread = Thread.currentThread();//两种判断方式都行while (!currentThread.isInterrupted()){//while(!Thread.interrupted())System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();Thread.sleep(3000);//在主线程中,控制 t 线程被终止,设置上述标志位t.interrupt();}
}

但是执行上述代码我们会发现程序运行是有问题的:

可以看出程序运行抛出了一个RuntimeException异常。主要是由于判断isInterrupted()和执行打印操作的速度过快,因此在整个循环中,主要时间都是处于sleep状态中,main调用interrupt()时,不仅仅会设置标志位,还会把sleep给唤醒,假如sleep刚执行了100ms,还剩900ms,此时interrupt被调用,sleep就会被强制唤醒,并且抛出interruptedException异常

又由于catch中的代码默认再次抛出了一个RuntimeException异常,而这个异常没有被处理,就会导致直接抛到JVM层面,使得进程直接异常终止了

这时我们尝试将代码改为:

这样不抛出新的异常而是输出一段语句是否就能让程序正常运行呢?

显然是不能的,我们可以看到在执行catch操作后,线程并没有被终止,仍然在不断地运行输出。

这是由于当sleep等阻塞函数被唤醒后,会清空刚刚设置的interruptded标志位,这样在线程的下次循环判断时,程序就会认为标志位任未被设置,从而继续执行下去了。

此时,如果想要结束循环,结束线程,就需要在catch中加上return/break语句:

这样,线程就能正常被终止了:

出现这样的现象主要是由于java中,线程终止是一个相对“温柔”的过程,并不是强行就终止。当a线程想让b线程终止时,b可以自行决定,是否要终止/是立即还是稍后,这些都由b线程内部的代码来决定,其他线程无权干涉。

比如:

(1)如果b线程想无视a线程的终止请求,就直接在catch中啥也不做,b仍然会继续执行

(2)如果b线程相要立即结束,就在catch中写入return或break,此时b线程就会立刻结束

(3)如果b线程想要稍后结束,就可以在catch上写入一些其他逻辑(如释放资源,清理一些数据,提交一些结果……等收尾工作),这些逻辑完成后,再进行return/break操作

这些全都得益于sleep这类阻塞方法强制唤醒时会清除标志位,才能让b做出各种选择,否则b将被强制结束,无法写出让程序继续执行的代码了。这样可以给程序员更多的操作空间


5、线程等待-join()

操作系统针对多个线程的执行,是一个“随机调度”,抢占式执行的过程。线程的调度执行是随机的,我们无法确定两个线程的调度顺序,但是可以控制谁先结束谁后结束。

线程等待就是确定两个线程的结束顺序,通过让后结束的线程等待先结束的线程执行,进入阻塞状态,直到先结束的线程执行完毕,此时阻塞解除,后结束的线程开始执行。这样就能使两个线程的结束顺序得以确定

这时就可以使用join关键字实现线程等待

比如有两个线程a,b,此时在a线程中调用b.join,意思就是让a线程等待b线程先结束,a再继续执行。通俗的来讲,就相当于是让b插入到a线程的执行过程中

不过也要注意a和b本质上还是两个线程,依旧是并发执行,只是确定了结束顺序

代码示例:

public class Demo {public static void main(String[] args) throws InterruptedException {Thread t=new Thread(()->{System.out.println("t 线程开始执行");for (int i = 0; i < 3; i++) {System.out.println("这是线程 t");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t 线程执行结束");});t.start();//让主线程等待 t 线程System.out.println("main 线程开始等待");t.join();System.out.println("main 线程等待结束");}
}

可以很明显地看到主线程是先开始执行的,但当执行到t.join()语句时,主线程开始阻塞等待 t 线程的执行,当 t 线程执行结束后,join才会返回,主线程才会继续执行后续代码。

不过如果 t 线程先执行完毕了,然后主线程才开始join,此时主线程不会出现阻塞等待,而是会正常执行:

注意

        上述操作都是无参数的join方法,就是“死等”,只要被等待的线程没有执行完毕,就会一直阻塞等待。这并不是一个好的选择,因为一旦被等待的线程代码出现bug,可能导致该线程迟迟无法结束,从而使等待线程一直阻塞而无法继续执行其它操作了

方法如下:

可以在join方法中加入参数来确定等待时间,如果等待时间超过设定时间,就会停止等待,退出阻塞状态继续执行后续代码了。


方法汇总:

 

那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。作者还是一个萌新,如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊

相关文章:

【多线程】初步认识Thread类及其应用

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;多线程 / javaEE初阶 上篇文章我们简单介绍了什么是进程与线程&#xff0c;以及他们之间的区别与联系&#xff0c;实际应用中还是以多线程编程为主的&#xff0c;所以这篇文章就让我们更加深入地去剖…...

algorithm算法库学习之——划分操作和排序操作

algorithm此头文件是算法库的一部分。本篇介绍划分操作和排序操作。 划分操作 is_partitioned (C11) 判断范围是否已按给定的谓词划分 (函数模板) partition 将范围中的元素分为两组 (函数模板) partition_copy (C11) 复制一个范围&#xff0c;将各元素分为两组 (函数模板) st…...

XSS实验记录

目录 XXS地址 实验过程 Ma Spaghet Jeff Ugandan Knuckles Ricardo Milos Ah Thats Hawt Ligma Mafia Ok, Boomer XXS地址 XSS Game - Learning XSS Made Simple! | Created by PwnFunction 实验过程 Ma Spaghet 要求我们弹出一个alert(1337)sandbox.pwnfuncti…...

Cortex-A7的GIC(全局中断控制器)使用方法(7):基于stm32MP135的GIC配置中断效果测试

0 参考资料 STM32MP13xx参考手册.pdf&#xff08;RM0475&#xff09; ARM Generic Interrupt Controller Architecture version 2.0 - Architecture Specification.pdf 1 GIC配置中断效果测试 前面我们已经实现了GIC的配置&#xff0c;为了验证GIC是否配置有效&#xff0c;本例…...

c++动态数组new和delete

文章目录 动态数组的使用大全1. **基本创建和初始化**2. **动态调整大小**3. **动态数组的使用与标准库 std::vector**4. **动态数组作为函数参数**输出 5. **使用动态数组存储用户输入** 动态数组的使用大全 1. 基本创建和初始化 示例&#xff1a; #include <iostream&g…...

Redis热点知识速览(redis的数据结构、高性能、持久化、主从复制、集群、缓存淘汰策略、事务、Pub/Sub、锁机制、常见问题等)

Redis是一个开源的、使用内存作为存储的、支持数据结构丰富的NoSQL数据库。它的高性能、灵活性和简单易用使其在许多场景下成为首选的缓存解决方案。以下是Redis的常见和热点知识总结。 数据结构 Redis支持五种基本数据结构&#xff1a; String&#xff1a;字符串是Redis中最…...

【C++浅析】lambda表达式:基本结构 使用示例

基本结构 [捕获列表](参数列表) -> 返回类型 { // 函数体 } 捕获列表 ([ ]): 用于指定外部变量的捕获方式。可以&#xff1a; 通过值捕获&#xff1a;[x]通过引用捕获&#xff1a;[&x]捕获所有变量通过值&#xff1a;[]捕获所有变量通过引用&#xff1a;[&]自…...

利用Redis获取权限的多种方式

更多实战内容&#xff0c;可前往无问社区查看http://www.wwlib.cn/index.php/artread/artid/10333.html Redis是我们在实战中经常接触到的一款数据库&#xff0c;因其在前期打点中被利用后可直接影响服务器安全所以在攻防过程中也备受红队关注&#xff0c;在本文中会重点分享一…...

LeetCode - LCR 146- 螺旋遍历二维数组

LCR 146题 题目描述&#xff1a; 给定一个二维数组 array&#xff0c;请返回「螺旋遍历」该数组的结果。 螺旋遍历&#xff1a;从左上角开始&#xff0c;按照 向右、向下、向左、向上 的顺序 依次 提取元素&#xff0c;然后再进入内部一层重复相同的步骤&#xff0c;直到提取完…...

如何获取Bing站长工具API密钥

Bing站长工具近期悄然上线了网站URL推送功能&#xff0c;似乎有意跟随百度的步伐。这个新功能允许站长通过API向Bing提交链接数据&#xff0c;当然也可以通过Bing站长工具手动提交。 本文将详细介绍如何通过Bing站长工具生成用于网站链接推送的API密钥。 首先&#xff0c;访问…...

NC 调整数组顺序使奇数位于偶数前面(一)

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 输入一个长度…...

Unity异步把图片数据从显存下载到内存(GPU->CPU)

Unity异步把图片数据从显存下载到内存&#xff08;GPU->CPU&#xff09; 1.c#核心代码 using System.Collections; using System.Collections.Generic; using Unity.Collections; using UnityEditor.PackageManager.Requests; using UnityEngine; using UnityEngine.Rende…...

【MySQL】C/C++连接MySQL客户端,MySQL函数接口认知,图形化界面进行连接

【MySQL】C/C引入MySQL客户端 安装mysqlclient库mysql接口介绍初始化mysql_init链接数据库mysql_real_connect下发mysql命令mysql_query获取出错信息mysql_error获取执行结果mysql_store_result获取结果行数mysql_num_rows获取结果列数mysql_num_fields判断结果列数mysql_field…...

Wireshark分析工具

简单用例 首先打开软件&#xff0c;左上角点文件&#xff0c;选中要分析的文件列表。 导入用tcpdump抓的包后进行分析&#xff0c;这里要输入过滤条件&#xff0c;对网络包进行一定的过滤处理。&#xff08;这里172网段是阿里云的地址&#xff0c;用自己写的python2脚本对阿里…...

linux网络配置脚本

通过脚本&#xff0c;设置静态ip以及主机名 因为企业9的网络配置文件和企业7的不一样所以&#xff0c;我们以rhel9和rhel7为例 rhel7/centos7/openeuler #!/bin/bash cat > /etc/sysconfig/network-scripts/ifcfg-$1 << EOF DEVICE$1 ONBOOTyes BOOTPROTOnone IPAD…...

IT管理:我与IT的故事4

首先&#xff0c;宣布一个“坏消息”。最近Herry童鞋的办公邮箱似乎有些“抽抽”了&#xff0c;所以邮件出现了延迟、拒收、被拒收、甚至是石沉大海的现象。为了能够更好的和大家进行沟通&#xff0c;大家如果发邮件到我办公邮箱的时候&#xff0c;若不嫌麻烦&#xff0c;可以抄…...

短链接系统设计方案

背景 需要设计一个短链接系统&#xff0c;主要功能主要有如下几点&#xff1a; ToB&#xff1a; 输入一个长链接&#xff0c;转换成短链接。这个短链接有时效性&#xff0c;可以设定指定过期时间。这个系统的每天会生成千万级别的短链接。数据具备可分析功能。 ToC&#xf…...

Cisco交换机SSH使用RSA公钥免密登录(IOS与Nexus,服务器以RHEL8为例)

目录 需求实验步骤0. 实验环境1. Linux2. CiscoIOS基础设置保存密钥登陆测试 3. CiscoNexus基础配置保存密钥登陆测试 需求 在实际工作中&#xff0c;常会遇到自动化的需求&#xff0c;那么在自动采集、配置等对网络设备的自动化需求中&#xff0c;不可避免的会遇到需要登录-&…...

QT判断操作系统类型和CPU架构

一、判断操作系统类型 1.在.pro文件中判断 macx { # mac only } unix:!macx{ # linux only } win32 { # windows only }2.在代码中判断 可以包含QGlobal头文件&#xff0c;判断预定义宏 #include <QtGlobal> ... #ifdef Q_OS_MAC // mac #endif#ifdef Q_OS_LINUX // …...

input[type=checkbox]勾选框自定义样式

效果图&#xff1a; <template> <input class"rule-checkbox" type"checkbox" checked v-model"isChecked" /> </template><script setup lang"ts"> import { ref } from vue; const isChecked ref(); </…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

C++.OpenGL (20/64)混合(Blending)

混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

python爬虫——气象数据爬取

一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用&#xff1a; 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests&#xff1a;发送 …...