Java-多线程

摘要

多线程编程是现代软件开发中的一个重要概念,它允许程序同时执行多个任务,提高了程序的性能和响应性。本博客深入探讨了多线程编程的关键概念、原理和最佳实践。

线程、进程、多线程、并发、并行

图片标题
  1. 进程

    • 进程是计算机中运行的程序的实例。每次打开一个应用程序,操作系统都会为其分配内存空间并创建一个新的进程。
    • 例如:QQ、微信等应用程序都是不同的进程。
  2. 线程

    • 线程是进程内的执行单元,一个进程可以包含多个线程。线程共享进程的资源和内存,但有自己的执行上下文。
    • 例如:在QQ中,同时进行文字聊天和视频通话可以看作是使用了多个线程来处理这两个功能。
  3. 多线程

    • 多线程是指在同一时刻,一个进程内可以有多个线程并发执行。这允许应用程序同时处理多个任务。
    • 例如:QQ可以同时打开多个聊天窗口,每个聊天窗口对应一个独立的线程,实现并发聊天功能;百度网盘可以同时下载多个文件,每个下载任务对应一个独立的线程,实现并发下载功能。
  4. 并发

    • 单核CPU:同一时刻,多个任务交替执行,如下图所示:
      图片标题
  5. 并行

    • 多核CPU:同一时刻,多个任务同时执行,如下图所示:
      图片标题

我们可以编写Java程序来查看自己电脑的CPU核数

Code

Runtime runtime = Runtime.getRuntime();
//获取当前操作系统的cpu核数
int cpuNums = runtime.availableProcessors();
System.out.println("当前CPU核数=" +cpuNums);输出: 当前CPU核数=6

线程7大状态

  1. New:线程对象已被创建,但尚未开始执行。此时线程尚未分配 CPU 时间片,处于等待状态。
  2. Runnable:线程可运行状态,表示线程已经准备好执行。在可运行状态下,线程可能处于以下两个子状态:
    • Ready:就绪状态
    • Running:运行状态
  3. TimedWaiting:线程因等待某个条件一段时间而进入等待状态。
  4. Waiting:线程因等待某个条件而进入等待状态。
  5. Blocked:线程被阻止等待某个锁资源。当线程试图访问一个已被其他线程占用的锁资源时,它会进入阻塞状态,直到锁资源可用。
  6. Terminated:线程已完成执行并终止。

创建线程的方式

在Java启动时,会默认创建两个线程:

  1. main线程:这是Java应用程序的主线程,是程序的入口点。main线程执行main方法中的代码,负责程序的初始化和执行。
  2. GC线程:这是Java虚拟机(JVM)内部的垃圾回收线程。它负责在后台自动回收不再使用的内存,以确保程序的内存管理。

创建线程的方法主要有以下两种:

  1. 继承Thread类: 您可以创建一个自定义的类,继承自Thread类,并重写run方法来定义线程的执行逻辑。然后,通过创建该类的实例并调用start方法来启动线程。
  2. 实现Runnable接口: 您可以创建一个实现了Runnable接口的类,实现run方法来定义线程的执行逻辑。然后,通过创建该类的实例,将其传递给Thread类的构造函数,并调用start方法来启动线程。

这两种方式都可以用于创建线程,但使用Runnable接口通常更灵活,因为它允许多个线程共享相同的Runnable实例,实现了解耦和代码复用。

以下是图示,展示了这两种线程创建方式的关系:

图片标题

继承Thread类

Code

public class MyThread extends Thread {@Overridepublic void run() {while(true){System.out.println("喵喵,我是小猫咪" + (++times));}}public static void main(String[] args) {   //main线程//创建一个线程对象MyThread myThread = new MyThread();myThread.start();        //开启新线程//main线程业务代码for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " " + i); }}
}输出: 
main 0 
喵喵,我是小猫咪1 
main 1 
main 2
喵喵,我是小猫咪2
。。。

分析Code

  1. 多核CPU:创建了main线程和myThread线程,并且同时执行任务。
  2. 线程执行的顺序是不确定的,具体由CPU决定。

优点

  1. 简单直观:继承Thread类创建线程相对简单,只需创建一个继承自Thread的子类并重写run方法,然后实例化子类对象并调用start方法即可启动线程。
  2. 易于理解:对于初学者来说,这种方式更容易理解和上手,因为它类似于面向对象编程的常规方式。

缺点

  1. 单继承限制:Java中不支持多重继承,因此如果您的线程已经继承自Thread类,就不能再继承其他类。这可能限制了您的代码组织和设计选择。
  2. 不利于共享资源:继承Thread类的方式不太适合多个线程之间共享相同的资源,因为每个线程都是一个独立的对象,不容易在多个线程之间共享数据。
  3. 不利于线程池管理:如果您使用线程池来管理线程,继承Thread类的方式可能不太适合,因为线程池更适合管理实现Runnable接口的任务。

💡为什么使用start()方法启动线程,而不是直接调用run()方法?

  1. myThread.start():创建一个新线程,实现多线程执行。
  2. myThread.run():相当于调用了一个方法,而没有真正的创建一个线程。

start()源码分析

以下是start()方法的底层源码,其中最核心的是start0()方法。

public synchronized void start() {。。。。。。start0();    //最核心的代码
}private native void start0();
  1. 首先调用最核心的代码,即调用start0()方法;

  2. 由于start0()是本地方法,有JVM调用,底层是c/c++实现,无法查看;

  3. 所有线程并不会马上执行,只是将线程状态改为可运行状态,具体什么时候执行,取决于CPU。

实现Runnable接口(推荐使用)

💡为什么推荐通过实现Runnable来创建线程?

  1. 因为Java是单继承机制,在某些情况下已经继承了其他父类,这时在继承Thread类来创建线程显然不可能了。
  2. Java设计者就提供了另外一种方式创建线程,通过实现Runnable接口创建线程。

Code
模拟抢票系统可以用多线程来模拟,每个线程代表一个用户尝试抢票。下面是一个简单的Java示例,使Runnable接口来实现一个基本的抢票系统模拟:

public class TicketSystem implements Runnable {private int totalTickets = 10; // 总票数@Overridepublic void run() {while (totalTickets > 0) {if (totalTickets > 0) {String threadName = Thread.currentThread().getName();System.out.println(threadName + " 抢到了第 " + totalTickets + " 张票");totalTickets--;} else {System.out.println("票已售罄");}try {Thread.sleep(100); // 模拟用户抢票间隔} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {TicketSystem ticketSystem = new TicketSystem();Thread user1 = new Thread(ticketSystem, "小明");Thread user2 = new Thread(ticketSystem, "张三");Thread user3 = new Thread(ticketSystem, "李四");user1.start();user2.start();user3.start();}
}输出:
李四抢到第10票 
小明抢到第9票 
。。。
张三抢到第2票 
小明抢到第2票 
李四抢到第1票
李四抢到第0票
小明抢到第-1

分析Code

从输出结果上可以看到有多线程的抢票系统存在以下几个问题:

  1. 张三抢到第2票和小明抢到第2票同时抢到了第2张票。
  2. 小明抢到第-1票

多个线程同一时刻访问了同一个资源,造成线程不安全

优点:

  1. 灵活性:实现Runnable接口比继承Thread类更灵活。一个类可以实现多个接口,因此您可以在一个类中实现Runnable接口,同时还可以继承其他类或扩展其他功能。
  2. 避免单继承限制:Java不支持多重继承,但可以实现多个接口。这使得通过实现Runnable接口创建线程更容易与其他类协作。
  3. 资源共享:通过实现Runnable接口,多个线程可以共享相同的实例变量,这使得资源共享更容易。
  4. 线程池管理:使用实现Runnable接口的线程更容易集成到线程池中,线程池可以更好地管理和重用线程。

缺点:

  1. 稍微复杂:相对于继承Thread类,实现Runnable接口的方式稍微复杂一些,需要在类中实现run方法,并且需要创建Runnable对象并传递给Thread类的构造函数。
  2. 更多的代码:需要编写更多的代码来创建和启动线程,因为您需要创建一个Runnable对象,然后将它传递给Thread对象。

线程同步机制

💡为什么有线程同步机制?

  1. 为了确保多个线程可以安全地访问和操作共享的资源,以防止出现竞态条件(Race Condition)和数据不一致的情况。如上述的抢票系统存在的问题

线程同步的原理

如上图所示:

  1. 多个线程:现在有多个人同时要上厕所;
  2. 队列:当有多个线程同时访问同一个资源时,CPU会将多个线程进行排序争夺资源(锁),只有得到锁的线程才能访问资源;
  3. :为了保证线程安全,每个线程在访问共享资源之前会尝试获取一个锁。锁充当门禁,只有持有锁的线程才能访问资源。

每个线程需要使用共享资源时,先尝试获取锁,如果锁被其他线程占用,则将该线程加入到队列中,等待获取锁,

使用完共享资源后,释放锁,线程重新排序队列,四个Person对象重新抢夺这个锁(已经使用的线程仍然可以继续排序争夺锁)

synchronized修饰同步方法/同步块实现同步机制

在Java中可以使用synchronized关键字创建同步方法/同步块实现线程同步机制。

同步方法

同步方法可以通过在方法声明中添加synchronized关键字来实现。

这会使得该方法在被多个线程访问时,只有一个线程能够执行该方法,其他线程需要等待该线程执行完成后才能继续执行。

Code

使用同步方法解决抢票问题

public class TicketSystem {private int totalTickets = 10; // 总票数// 使用同步方法确保线程安全public synchronized void buyTicket(String threadName) {if (totalTickets > 0) {System.out.println(threadName + " 抢到了第 " + totalTickets + " 张票");totalTickets--;} else {System.out.println("票已售罄");}}public static void main(String[] args) {TicketSystem ticketSystem = new TicketSystem();Thread user1 = new Thread(() -> {ticketSystem.buyTicket("张三");});Thread user2 = new Thread(() -> {ticketSystem.buyTicket("小明");});Thread user3 = new Thread(() -> {ticketSystem.buyTicket("李四");});user1.start();user2.start();user3.start();}
}

在这个示例中,buyTicket方法被定义为同步方法,确保了线程安全。多个用户线程(张三、小明、李四)同时尝试调用buyTicket方法,但只有一个线程能够成功抢到票,其他线程会等待。

同步块

同步块可以通过在代码块前添加**synchronized**关键字来实现。

这会使得该代码块在被多个线程访问时,只有一个线程能够执行该代码块,其他线程需要等待该线程执行完成后才能继续执行。

Code

使用同步块解决抢票问题

public class TicketSystem implements Runnable {private int totalTickets; // 总票数private int interval;     // 抢票间隔(毫秒)public TicketSystem(int totalTickets, int interval) {this.totalTickets = totalTickets;this.interval = interval;}@Overridepublic void run() {while (true) {synchronized (this) { // 使用同步块确保线程安全if (totalTickets > 0) {String threadName = Thread.currentThread().getName();System.out.println(threadName + " 抢到了第 " + totalTickets + " 张票");totalTickets--;} else {System.out.println("票已售罄");break; // 所有票已售完,退出循环}}try {Thread.sleep(interval); // 模拟用户抢票间隔} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {int totalTickets = 10;int interval = 100;TicketSystem ticketSystem = new TicketSystem(totalTickets, interval);Thread user1 = new Thread(ticketSystem, "张三");Thread user2 = new Thread(ticketSystem, "小明");Thread user3 = new Thread(ticketSystem, "李四");user1.start();user2.start();user3.start();}
}

这个示例中,我将总票数和抢票间隔作为构造函数参数传递给 TicketSystem 类,使代码更具通用性。

死锁

多个线程各自占有一些共享的资源,并且相互等待其他线程占有的资源才能运行,从而导致两个线程都在等待对象释放资源。
图片标题

  1. A线程:正在使用对象1,但是此时需要访问对象2 ;
  2. B线程:正在使用对象2 ,但是此时需要访问对象1;
  3. 两个线程都在等待对方释放资源,导致线程阻塞
  4. 若无外部干涉,它们都将无法继续执行下去,称为死锁

Lock锁

在 Java 中,Lock 锁是一种用于多线程编程的机制,它提供了比传统的 synchronized 关键字更灵活和强大的线程同步和互斥控制方式。

  1. Lock 接口定义了一套用于获取和释放锁的方法,可以手动开启和关闭锁。
  2. 最常用的实现类是 ReentrantLock

Code

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class TicketSystem implements Runnable {private int totalTickets = 10; // 总票数private Lock lock = new ReentrantLock(); // 创建一个ReentrantLock锁@Overridepublic void run() {while (true) {try {lock.lock(); // 获取锁if (totalTickets > 0) {String threadName = Thread.currentThread().getName();System.out.println(threadName + " 抢到了第 " + totalTickets + " 张票");totalTickets--;} else {System.out.println("票已售罄");break; // 所有票已售完,退出循环}} finally {lock.unlock(); // 释放锁}try {Thread.sleep(100); // 模拟用户抢票间隔} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {TicketSystem ticketSystem = new TicketSystem();Thread user1 = new Thread(ticketSystem, "张三");Thread user2 = new Thread(ticketSystem, "小明");Thread user3 = new Thread(ticketSystem, "李四");user1.start();user2.start();user3.start();}
}

在这个示例中,我们使用了 ReentrantLock 锁来控制对共享资源的访问。在 run 方法中,通过 lock.lock() 获取锁,在访问共享资源后使用 lock.unlock() 释放锁,以确保线程安全。

优点

  1. 使用 Lock 锁的优势在于它提供了更多的控制,例如可以设置锁的超时时间、使用条件变量等.

缺点

  1. 但需要注意,与 synchronized 关键字相比,使用 Lock 锁需要手动释放锁,因此需要在 finally 块中确保锁的释放,以防止出现死锁等问题。

线程常用方法

方法说明
setName()设置线程名称
getName()返回该线程名称
start()该线程开始执行,JVM调用start0()方法
run()调用线程对象run()方法
setPriority()更改线程的优先级
getpriority()获取线程的优先级
sleep()线程休眠
interrupt()中断线程
yield()线程礼让,但是不能保证线程礼让成功
join()线程插队

线程终止

  1. 当线程完成任务后,会自动退出;
  2. 可以通过使用变量来控制run()方法退出,终止线程(通知方式)。
  3. 不建议使用stop或者destory等过时的方法

Code

public class ThreadStop implements Runnable {private boolean isRunning = true;   //线程停止标志位@Overridepublic void run() {int i = 0;while (isRunning) {System.out.println("正在执行..." + i++);}}//暂停线程方法public void stopThread() {isRunning = false;}public static void main(String[] args) throws InterruptedException {ThreadStop threadStop = new ThreadStop();//创建线程,启动线程new Thread(threadStop).start();Thread.sleep(2000);threadStop.stopThread();}
}

线程在执行任务时会不断检查自己的终止状态,如果isRunning=false,则终止线程。

在main方法中,启动了一个线程,并在2秒后将isRunning=false,终止线程。

线程中断(interrupt)

线程中断是指一个线程向另一个线程发出信号,请求其停止正在执行的操作。

这个信号由一个布尔标志来表示,通常称为线程的中断状态(interrupt status)。

当线程的中断状态被设置为**true**时,线程会收到一个中断请求。

Code

class MyRunnable implements Runnable {@Overridepublic void run() {while (!Thread.currentThread().isInterrupted()) {   // 线程执行任务try {Thread.sleep(1000); // 模拟工作} catch (InterruptedException e) {// 响应中断请求,可以进行清理工作Thread.currentThread().interrupt(); // 重新设置中断状态}}}
}public class ThreadInterruptExample {public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start();         //开启线程// 在某个时刻中断线程try {Thread.sleep(5000);thread.interrupt();    // 发送中断信号,设置为true} catch (InterruptedException e) {e.printStackTrace();}}
}

线程在执行任务时会不断的检查自己的中断状态,如果中断状态为true,则推出任务执行。

在“main”方法中,启动了一个线程,并且在5秒后发送中断请求。

线程插队(join)

线程一旦插队成功,则肯定先执行插入的线程所有的任务,再去执行其他线程的任务。

Code

public class ThreadStop implements Runnable {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("vip线程来了," + i);}}public static void main(String[] args) throws InterruptedException {ThreadStop threadStop = new ThreadStop();Thread thread = new Thread(threadStop, "A");thread.start();//main线程for (int i = 0; i < 500; i++) {if (i == 100) {thread.join();         //线程插队}System.out.println("main线程," + i);}}
}

main线程在循环100次后,插入vip线程,等待vip线程执行完后,再继续执行main线程的任务。

礼让线程(yield)

Code

public class ThreadStop implements Runnable {@Overridepublic void run() {System.out.println("正在执行" + Thread.currentThread().getName() + "线程");Thread.yield();         //线程礼让System.out.println("停止" + Thread.currentThread().getName() + "线程");}public static void main(String[] args) throws InterruptedException {ThreadStop threadStop = new ThreadStop();new Thread(threadStop, "A").start();new Thread(threadStop, "B").start();}
}输出:
正在执行A线程 
正在执行B线程
停止B线程
停止A线程

main方法中,我们创建了两个线程实例(线程"A"和线程"B"),它们都使用相同的ThreadStop对象作为任务,并启动这两个线程。

由于两个线程共享ThreadStop对象,它们运行相同的run()方法。

由于Thread.yield()方法的存在,这两个线程在执行过程中可能会主动让出CPU时间,以便其他线程有机会运行。

守护线程

  1. 线程可以分为:用户线程守护线程(上帝线程)
  2. JVM必须确保用户线程执行完成;
  3. JVM不需要等待守护线程执行完毕。
public class ThreadStop {public static void main(String[] args) throws InterruptedException {God god = new God();Person person = new Person();//创建上帝线程Thread godThread = new Thread(god);godThread.setDaemon(true);   //设置为守护线程godThread.start();//创建用户线程Thread personThread = new Thread(person);personThread.start();}
}class God implements Runnable {@Overridepublic void run() {while (true) {System.out.println("正在执行守护线程");}}
}class Person implements Runnable {@Overridepublic void run() {for (int i = 0; i < 20; i++) {System.out.println("用户岁数=" + i);}System.out.println("======say goodbye======");}
}输出:
正在执行守护线程
用户岁数=0
用户岁数=1
用户岁数=2
用户岁数=3
用户岁数=4
用户岁数=5
用户岁数=6
用户岁数=7
用户岁数=8
用户岁数=9
用户岁数=10
用户岁数=11
用户岁数=12
用户岁数=13
用户岁数=14
用户岁数=15
用户岁数=16
用户岁数=17
用户岁数=18
用户岁数=19
======say goodbye======
正在执行守护线程
正在执行守护线程
正在执行守护线程
正在执行守护线程
正在执行守护线程
正在执行守护线程

当用户线程执行完毕后,守护线程也会紧随其后。

结语

多线程编程是现代软件开发不可或缺的一部分,但也存在复杂性和挑战。

通过深入理解多线程的原理和最佳实践,开发人员可以更好地利用多核处理器,提高程序性能和响应性,同时避免潜在的线程安全问题。

本博客提供了一个较为基础的多线程编程指南,帮助开发人员入门这一重要领域的技能。

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

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

相关文章

《人间失格》阅读笔记

《人间失格》读书笔记 2023年10月7日读完&#xff0c;在过去的三个月时间内&#xff0c;有忙碌申博、从杭州辞职回家、准备入学、到澳门入学的事情&#xff0c;终于忙完了这些所有事情&#xff0c;回到了横琴的小房子里读完了这本书。 这本书前半部分讲了主角&#xff0c;作为…

Delphi编程:pagecontrol组件的tab字体变大

1、将pagecontrol组件属性中的font的字体变成四号。 2、将tabsheet1属性中的font的字体设置成八号。 结果如下&#xff1a;

水果种植与果园监管“智慧化”,AI技术打造智慧果园视频综合解决方案

一、方案背景 我国是水果生产大国&#xff0c;果园种植面积大、产量高。由于果园的位置大都相对偏远、面积较大&#xff0c;值守的工作人员无法顾及到园区每个角落&#xff0c;因此人为偷盗、野生生物偷吃等事件时有发生&#xff0c;并且受极端天气如狂风、雷暴、骤雨等影响&a…

山东省赛二阶段第一部分解题思路

提交攻击者的IP地址 192.168.1.7 这个直接awk过滤一下ip次数&#xff0c;这个ip多得离谱&#xff0c;在日志里面也发现了它的恶意行为&#xff0c;后门&#xff0c;反弹shell 识别攻击者使用的操作系统 Linux 找出攻击者资产收集所使用的平台 shodan 提交攻击者目…

C#,数值计算——数据建模Fitab的计算方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// Fitting Data to a Straight Line /// </summary> public class Fitab { private int ndata { get; set; } private double a { get; set; } …

CTF之信息收集

什么是信息收集 信息收集是指通过各种方式获取所需要的信息&#xff0c;以便我们在后续的渗透过程更好的进行。最简单的比如说目标站点的IP、中间件、脚本语言、端口、邮箱等等。我觉得信息收集在我们参透测试的过程当中&#xff0c;是最重要的一环&#xff0c;这一环节没做好…

Java-Exception

目录 异常概念ErrorException 体系图常见运行时异常NullPointerExceptionArithmeticExceptionArrayIndexOutOfBoundExceptionClassCastExceptionNumberFormatException 常见的编译异常异常处理机制自定义异常throw和throws对比 异常是Java编程中的常见问题&#xff0c;了解如何…

nsoftware Cloud SMS 2022 .NET 22.0.8 Crack

nsoftware Cloud SMS 能够通过各种流行的消息服务&#xff08;包括 Twilio、Sinch、SMSGlobal、SMS.to、Vonage、Clickatell 等&#xff09;发送、接收和安排 SMS 消息&#xff0c;从而提供了一种简化且高效的消息服务方法。 Cloud SMS 提供单个 SMS 组件&#xff0c;允许通过…

JDBC-day02(使用PreparedStatement实现CRUD操作)

所需的数据库数据要导入到自己的数据库库中 三&#xff1a;使用PreparedStatement实现CRUD操作 数据库连接被用于向数据库服务器发送命令和 SQL 语句&#xff0c;并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。CRUD操作&#xff1a;根据返回值的有无…

【Go】go-es统计接口被刷数和ip访问来源

go-es模块统计日志中接口被刷数和ip访问来源 以下是使用go的web框架gin作为后端&#xff0c;展示的统计页面 背景 上面的数据来自elk日志统计。因为elk通过kibana进行展示&#xff0c;但是kibana有一定学习成本且不太能满足定制化的需求&#xff0c;所以考虑用编程的方式…

Eclipse iceoryx™ - 真正的零拷贝进程间通信

1 序言 通过一个快速的背景教程&#xff0c;介绍项目范围和安装所需的所有内容以及第一个运行示例。 首先&#xff1a;什么是冰羚&#xff1f; iceoryx是一个用于各种操作系统的进程间通信&#xff08;IPC&#xff09;中间件&#xff08;目前我们支持Linux、macOS、QNX、FreeBS…

C语言中文网 - Shell脚本 - 1

Shell 既是一个连接用户和 Linux 内核的程序&#xff0c;又是一门管理 Linux 系统的脚本语言。Shell 脚本虽然没有 C、Python、Java、C# 等编程语言强大&#xff0c;但也支持了基本的编程元素。 第1章 Shell基础&#xff08;开胃菜&#xff09; 欢迎来到 Linux Shell 的世界&am…

asp.net闲置物品购物网系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net闲置物品购物网系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语 言开发 asp.net 闲置物品购物网 二、功…

JavaScript中的map()和forEach()方法有什么区别?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

DevEco Studio下载/安装与配置开发环境

一、下载与安装DevEco Studio 在HarmonyOS应用开发学习之前&#xff0c;需要进行一些准备工作&#xff0c;首先需要完成开发工具DevEco Studio的下载与安装以及环境配置。 1.进入DevEco Studio下载官网 单击“立即下载”进入下载页面。 DevEco Studio提供了Windows版本和Mac…

学信息系统项目管理师第4版系列20_风险管理

1. 针对不确定性的应对方法 1.1. 【高23上选58】 1.2. 收集信息 1.2.1. 可以对信息收集和分析工作进行规划&#xff0c;以便发现更多信息&#xff08;如进行研究、争取专家参与或进行市场分析&#xff09;来减少不确定性 1.3. 为多种结果做好准备 1.3.1. 制定可用的解决方…

高级 I/O【Linux】

阅读前导&#xff1a; “高级 I/O”处于知识树中网络和操作系统的最后&#xff0c;因此本文默认读者有计算机网络和操作系统的基础。 1. 什么是 I/O 下面以“流”&#xff08;stream&#xff09;和冯诺依曼体系架构的视角来简单回顾一下什么是 I/O&#xff1a; I/O可以理解…

2023全新小红书图集和视频解析去水印网站源码

2023全新小红书图集和视频解析去水印网站源码 小红书视频图集解析网站源码&#xff0c;在红书看到好看的图片以及好看的头像&#xff0c;但是直接下载又有水印就非常难受&#xff0c;这个可以一键解析去除水印&#xff0c;支持统计解析次数&#xff0c;本地接口。 源码下载&a…

【C++】指针与引用(学习笔记)

一、左值与右值 左值&#xff1a;编译器为其单独分配了一块存储空间&#xff0c;可以取其地址的&#xff0c;可以放在赋值运算符左边 右值&#xff1a;数据本身。不能取到其自身地址&#xff0c;只能赋值运算右边 左值最常见的情况如西数和数据成员的名字 右值是没有标识符、…

k8s 集群安装(vagrant + virtualbox + CentOS8)

主机环境&#xff1a;windows 11 k8s版本&#xff1a;v1.25 dashboard版本&#xff1a;v2.7.0 calico版本&#xff1a; v3.26.1 CentOS8版本&#xff1a;4.18.0-348.7.1.el8_5.x86_64 用到的脚本&#xff1a; https://gitcode.net/sundongsdu/k8s_cluster 1. Vagrant创建…