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

Java 多线程(超详细讲解)上篇

多线程可以使程序在同一时间内执行多个操作,采用Java中的多线程机制可以使计算机资源得到更充分的利用,多线程技术在网络编程中有广泛的应用。

一、进程与线程

进程是程序的一次动态执行过程,它是从代码加载、执行中到执行完毕的一个完整过程,也就是进程本身从产生、发展到最终消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用中央处理器资源,或者共享操作系统的其他资源。由于CPU执行速度非常快,所有程序好像在“同时”运行一样。
在操作系统中可以多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程)。可以从Windows任务管理器中查看已启动的进程,进程是系统运行程序的最小单元。各进程之间是独立的,每个进程的内部数据和状态也是完全独立的。
线程是进程中执行运算的最小单位,是在进程基础上的进一步划分,一个线程可以完成一个独立的顺序控制流程。
与进程不同,同一进程内的多个线程共享同一块内存空间(包括代码空间、数据空间)和一块系统资源,所以系统在产生一个线程或在各线程之间切换工作,其负担要比在进程间切换小得多。

二、线程的优势

多线程作为一种多任务并发的工作方式,有着广泛的应用。合理使用线程,将减少开发和维护的成本,甚至可以改善复杂应用程序的性能。使用多线程的优势如下:
  1. 充分利用CPU的资源
  2. 简化编程模型
  3. 良好的用户体验

三、多线程编程

在Java语言中,实现多线程的方式有两种:一种是继承Thread类,另一种是实现Runnable接口。

1、Thread类介绍

Thread类提供了大量的方法来控制和操作线程。

方法描述类型
Thread()创建Thread对象构造方法
Thread(Runnable target)创建Thread对象,target为run()方法被调用的对象构造方法
Thread(Runnable target,String name)创建Thread对象,target为run()方法被调用的对象,name为新线程的名称构造方法
void run()执行任务操作的方法实例方法
void start()使该线程开始运行,JVM将调用该线程的run()方法实例方法
void sleep(long millis)在指定的毫秒数内让当前正在运行的线程休眠(暂停运行)静态方法
Thread currentThread()返回当前线程对象的引用静态方法

Thread类的静态方法currentThread()返回当前线程对象的引用。在Java程序启动时,一个线程立即随之启动,这个线程通常被称为程序的主线程。
Thread类的重要性:

  1. 主线程是产生其他子线程的线程
  2. 主线程通常必须最后完成运行,因为它执行各种关闭动作
    示例:
public class MainThreadTest {public static void main(String[] args) {Thread t=Thread.currentThread();System.out.println("当前线程:"+t.getName());t.setName("MainThread");System.out.println("当前线程:"+t.getName());}
}

以上示例中,使用Thread currentThread()方法获取当前线程,即主线程的Thread对象。在Thread中定义了name属性,记录线程名称,可以使用setName()方法或getName()方法对线程名称进行赋值和取值操作。

继承Thread类创建线程类

继承Thread类是实现线程的一种方式。在使用此方法自定义线程类时,必须在格式上满足如下要求:

  • 此类必须继承Thread类
  • 将线程执行的代码写在run()方法中

语法结构:

//继承Thread类的方式创建自定义线程类
public class MyThread extends Thread{//重写Thread类中的run()方法public void run(){//线程执行任务的代码}
}

示例:

public class WorkThread extends Thread{public void run(){System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");for (int i = 0; i <5 ; i++) {System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");}}
class ThreadTest{public static void main(String[] args) {WorkThread workThread=new WorkThread();workThread.setName("果农A");workThread.start();WorkThread workThread2=new WorkThread();workThread2.setName("果农B");workThread2.start();}
}

运行结果:

果农B开始采摘苹果树:
果农A开始采摘苹果树:
果农B进度:第1颗
果农A进度:第1颗
果农B进度:第2颗
果农A进度:第2颗
果农A进度:第3颗
果农B进度:第3颗
果农B进度:第4颗
果农A进度:第4颗
果农B进度:第5颗
果农A进度:第5颗
果农B已完成采摘任务!
果农A已完成采摘任务!

从以上示例可以看出,两个线程对象调用start()方法启动后,每个线程都会独立完成各自的线程操作,相互之间没有影响并行运行。

实现Runnable接口创建线程类

Runnable接口位于java.lang包中,其中只提供一个抽象方法run()的声明,Thread类也实现了Runnable接口。使用Runnable接口时离不开Thread类,这是因为它要用Thread类中的start()方法。在Runnable接口中只有run()方法,其他操作都要借助于Thread类。

语法结构:

//实现Runnable接口方式创建线程类
class MyThread implements Runnable{public void run(){//这里写线程内容}
}
//测试类
public class RunnableTest{public static void main(String[] args){//通过Thread类创建线程对象MyThread myThread=new MyThread();Thread thread=new Thread(myThread);thread.start();}
}

在上面的代码中,MyThread类实现了Runnable接口,在run()方法中编写线程所执行的代码。如果MyThread还需要继承其他类(如Base类),也完全可以实现。关键代码如下:

class MyThread extends Base implements Runnable{public void run(){//线程执行任务的代码}
}

示例:

public class OneGroupThread extends Thread {public void run() {System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + "已完成任务!");}
}class TwoGroupThread extends Thread {public void run() {System.out.println(Thread.currentThread().getName() + "开始为苹果树剪枝");for (int i = 0; i < 7; i++) {System.out.println(Thread.currentThread().getName() + "进度:第" + (i + 1) + "颗");try {Thread.sleep(300);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName() + "已完成任务!");}
}class Test {public static void main(String[] args) {OneGroupThread thread = new OneGroupThread();TwoGroupThread thread2 = new TwoGroupThread();thread.setName("一组阿哲");thread.start();thread2.setName("二组洋洋");thread2.start();}
}}
}

运行结果:

一组阿哲开始为苹果树剪枝
二组洋洋开始为苹果树剪枝
一组阿哲进度:第1颗
二组洋洋进度:第1颗
二组洋洋进度:第2颗
一组阿哲进度:第2颗
二组洋洋进度:第3颗
二组洋洋进度:第4颗
一组阿哲进度:第3颗
二组洋洋进度:第5颗
一组阿哲进度:第4颗
二组洋洋进度:第6颗
二组洋洋进度:第7颗
一组阿哲进度:第5颗
二组洋洋已完成任务!
一组阿哲已完成任务!

2、线程调度相关方法

方法描述
int getPriority()返回线程的优先级
void setPrority(int newPriority)更改线程的优先级
boolean isAlive()测试线程是否处于活动状态
void join()进程中的其他线程必须等待该线程终止后才能运行
void interrupt()中断线程
void yield()暂停当前正在执行的线程类对象并运行其他线程

1、线程的优先级

范围是1~10;可以使用数字也可以使用以下三个静态常量

  1. MAX_PRIORITY:其值是10,表示优先级最高。
  2. MIN_PRIORITY:其值是1,表示优先级最低。
  3. NORM_PRIORITY:其值是5,表示普通优先级,也就是默认值。

优先级并不代表永远是该线程一直有运行的机会,而是,会获得更多的运行机会,优先级低,也是会获得运行机会的。

示例:

public class WorkThread2 implements Runnable{public void run(){System.out.println(Thread.currentThread().getName()+"开始采摘苹果树:");for (int i = 0; i <5 ; i++) {System.out.println(Thread.currentThread().getName()+"进度:第"+(i+1)+"颗,优先级:"+Thread.currentThread().getPriority());try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println(Thread.currentThread().getName()+"已完成采摘任务!");}}
class RunnableTest{public static void main(String[] args) {WorkThread2 work1=new WorkThread2();Thread t1=new Thread(work1,"果农A");WorkThread2 work2=new WorkThread2();Thread t2=new Thread(work2,"果农B");WorkThread2 work3=new WorkThread2();Thread t3=new Thread(work3,"果农C");
//        Thread t1=new Thread(new WorkThread2());
//        Thread t2=new Thread(new WorkThread2());t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t3.setPriority(Thread.NORM_PRIORITY);t1.start();t2.start();t3.start();}
}

运行结果:

果农A开始采摘苹果树:
果农B开始采摘苹果树:
果农B进度:第1,优先级:1
果农C开始采摘苹果树:
果农A进度:第1,优先级:10
果农C进度:第1,优先级:5
果农A进度:第2,优先级:10
果农C进度:第2,优先级:5
果农B进度:第2,优先级:1
果农A进度:第3,优先级:10
果农C进度:第3,优先级:5
果农B进度:第3,优先级:1
果农A进度:第4,优先级:10
果农B进度:第4,优先级:1
果农C进度:第4,优先级:5
果农A进度:第5,优先级:10
果农B进度:第5,优先级:1
果农C进度:第5,优先级:5
果农C已完成采摘任务!
果农B已完成采摘任务!
果农A已完成采摘任务!

2、线程的强制运行

在线程操作中,可以使用join()方法让一个线程强制运行。在线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续运行。它有三个重载方法,定义如下:

public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)

注意:调用join()方法需要处理InterruptedException异常。

示例:

public class JoinThread implements Runnable {public void run() {for (int i = 0; i < 10; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");}}
}
//测试类
class Test3 {public static void main(String[] args) {//创建子线程并启动Thread t=new Thread(new JoinThread(),"大型商超");t.start();Thread.currentThread().setName("果商");//修改主线程名称//正常采购for (int i = 0; i < 10; i++) {if (i==5){try {t.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "采购进度:第" + (i + 1) + "车");}}
}

运行结果:

果商采购进度:第1车
大型商超采购进度:第1车
果商采购进度:第2车
大型商超采购进度:第2车
果商采购进度:第3车
大型商超采购进度:第3车
大型商超采购进度:第4车
果商采购进度:第4车
果商采购进度:第5车
大型商超采购进度:第5车
大型商超采购进度:第6车
大型商超采购进度:第7车
大型商超采购进度:第8车
大型商超采购进度:第9车
大型商超采购进度:第10车
果商采购进度:第6车
果商采购进度:第7车
果商采购进度:第8车
果商采购进度:第9车
果商采购进度:第10

在以上代码中,创建子线程类对象t代表大型商超,主线程代表果商。当向果商供应了五车水果后,改为向大型商超集中供应。在代码中,程序开始运行后,代码果商的主线程和代表大型商超的子线程交替运行。当条件满足后,执行t.join()方法,子线程会夺得CPU使用权,优先运行,子线程全部运行完毕后,代表果商的主线程恢复运行。

3、线程的礼让

当一个线程在运行中执行了Thread类的yield()静态方法后,如果此时还有相同或更高优先级的其他线程处于就绪状态,系统将会选择其他相同或更高优先级的线程运行,如果不存在这样的线程,则该线程继续运行。

注意:使用yield()方法实现线程礼让只是提供一种可能,不能保证一定会实现礼让,因为礼让的线程处于就绪状态时,还有可能被线程调度程序再次选中。

示例:

public class ChildThread implements Runnable {public void run() {for (int i = 0; i < 5; i++) {Thread.yield();//线程礼让System.out.println(Thread.currentThread().getName() + "品尝:第" + (i + 1) + "块");}}
}class Test4 {public static void main(String[] args) {Thread t1=new Thread(new ChildThread(),"Child1");Thread t2=new Thread(new ChildThread(),"Child2");Thread t3=new Thread(new ChildThread(),"Child3");t1.start();t2.start();t3.start();}
}

运行结果:

Child3品尝:第1Child2品尝:第1Child1品尝:第1Child2品尝:第2Child3品尝:第2Child3品尝:第3Child3品尝:第4Child1品尝:第2Child3品尝:第5Child2品尝:第3Child1品尝:第3Child2品尝:第4Child1品尝:第4Child2品尝:第5Child1品尝:第5

可以看出执行Thread.yield()方法之后,多线程交替运行较为频繁,提高了程序的并发性。

注意:
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU使用权,将运行机会让给其他线程,两者的区别如下:
1.sleep()方法会给其他线程运行机会,不考虑其他线程的优先级,因此较低优先级线程可能会获得运行机会。
2.yield()方法只会将运行机会让给相同优先级或更高优先级的线程。
3.调用sleep()方法需处理InterruptedException异常,而调用yeild()方法无此要求。

相关文章:

Java 多线程(超详细讲解)上篇

多线程可以使程序在同一时间内执行多个操作&#xff0c;采用Java中的多线程机制可以使计算机资源得到更充分的利用&#xff0c;多线程技术在网络编程中有广泛的应用。一、进程与线程 进程是程序的一次动态执行过程&#xff0c;它是从代码加载、执行中到执行完毕的一个完整过程…...

15届蓝桥杯备赛(2)

文章目录 刷题笔记(2)二分查找在排序数组中查找元素的第一个和最后一个位置寻找旋转排序数组中的最小值搜索旋转排序数组 链表反转链表反转链表II 二叉树相同的树对称二叉树平衡二叉树二叉树的右视图验证二叉搜索树二叉树的最近公共祖先二叉搜索树的最近公共祖先二叉树层序遍历…...

使用Vuex构建网络打靶成绩管理系统及其测试页面平台思路

使用Vuex构建网络打靶成绩管理系统及其测试页面平台 一、引言 在现代Web开发中&#xff0c;前端框架和状态管理库已经成为构建复杂应用的关键工具。Vue.js作为一个轻量级且易于上手的前端框架&#xff0c;结合Vuex这个专门为Vue.js设计的状态管理库&#xff0c;可以让我们更加…...

CPU的核心数与线程数对性能的影响是什么

我们经常在CPU的配置参数中看到核心数和线程数&#xff0c;那你知道CPU的核心数与线程数对性能的影响是什么呢&#xff1f;核心数和线程数是越多越好吗&#xff1f;要弄清楚这个问题&#xff0c;我们必须先了解以下几个基础知识。 什么是CPU核心&#xff1f; CPU核心&#xf…...

Web前端-HTML

HTML 负责页面的结构&#xff08;页面的元素和内容&#xff09; HTML由标签组成&#xff0c;标签都是预定义好的。例如<a>展示超链接&#xff0c;使用<img>展示图片&#xff0c;<vedio>展示视频。 HTML代码直接在浏览器中运行&#xff0c;HTML标签由浏览器…...

【LLMs+小羊驼】23.03.Vicuna: 类似GPT4的开源聊天机器人( 90%* ChatGPT Quality)

官方在线demo: https://chat.lmsys.org/ Github项目代码&#xff1a;https://github.com/lm-sys/FastChat 官方博客&#xff1a;Vicuna: An Open-Source Chatbot Impressing GPT-4 with 90% ChatGPT Quality 模型下载: https://huggingface.co/lmsys/vicuna-7b-v1.5 | 所有的模…...

详细了解CSS

1.1 样式定义方式 行内样式表&#xff08;inline style sheet&#xff09; 直接定义在标签的style属性中。 作用范围&#xff1a;仅对当前标签产生影响。 例如&#xff1a; <img src"/images/mountain.jpg" alt"" style"width: 300px; height:…...

Java基础-IO流

文章目录 1.文件1.基本介绍2.常用的文件操作1.创建文件的相关构造器和方法代码实例结果 2.获取文件相关信息代码实例结果 3.目录的删除和文件删除代码实例 2.IO流原理及分类IO流原理IO流分类 3.FileInputStream1.类图2.代码实例3.结果 4.FileOutputStream1.类图2.案例代码实例 …...

MySQL的基本概念

一.MySQL概念&#xff1a; 你可以把MySQL想象成一个大杂货店&#xff0c;里面有很多货架&#xff0c;每个货架上摆放着不同种类的商品&#xff0c;MySQLMySQ就像是这个杂货店的后台库存管理系统。 1.表格&#xff08;货架&#xff09;&#xff1a;每个货架上摆放商品&#xff0…...

如何入职车载测试

以下课件都可以学习&#xff0c;一对一教你如何入职车载 可以学习的内容如下&#xff1a;第一&#xff1a;仪表项目、导航项目、车控项目、OTA升级项目、UDS诊断项目。第二&#xff1a;DBC数据库制作、CDD数据库制作。第三&#xff1a;项目规范文档阅读、调查表理解。第四&…...

【物联网】Modbus 协议简介

Modbus 协议简介 QingHub设计器在设计物联网数据采集时不可避免的需要针对Modbus协议的设备做相关数据采集&#xff0c;这里就我们的实际项目经验分享Modbus协议 你可以通过QingHub作业直接体验试用&#xff0c;也可以根据手册开发相应的代码块。 qinghub项目已经全面开源。 …...

网络编程-套接字相关基础知识

1.1. Socket简介 套接字&#xff08;socket&#xff09;是一种通信机制&#xff0c;凭借这种机制&#xff0c; 客户端<->服务器 模型的通信方式既可以在本地设备上进行&#xff0c;也可以跨网络进行。 Socket英文原意是“孔”或者“插座”的意思&#xff0c;在网络编程…...

基于Python的医疗机构药品及耗材进销存信息管理系统

技术&#xff1a;pythonmysqlvue 一、系统背景 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本医疗机构药品及耗材信息管理系统就是在这样的大环境下诞生&#x…...

Java学习笔记(14)

常用API Java已经写好的各种功能的java类 Math Final修饰&#xff0c;不能被继承 因为是静态static的&#xff0c;所以使用方法不用创建对象&#xff0c;使用里面的方法直接 math.方法名 就行 常用方法 Abs,ceil,floor,round,max,minm,pow,sqrt,cbrt,random Abs要注意参数的…...

联合和枚举

联合体 联合体和结构体类似&#xff0c;也有多个成员构成&#xff0c;但编译器只为最大的成员分配足够的空间。 联合体最大的特点是所有的成员共用同一块内存空间。也叫共用体。 union Un { int i; struct s { char c1; char c2; char c…...

《深入Linux内核架构》第2章 进程管理和调度 (3)

目录 2.5 调度器的实现 2.5.1 概观 2.5.2 数据结构 2.5.3 处理优先级 2.5.3.1 nice和prior 2.5.3.2 vruntime 2.5.3.3 weight权重 2.5.4 核心调度器 2.5 调度器的实现 调度器的任务&#xff1a; 1. 执行调度策略。 2. 执行上下文切换。 无论用户态抢占&#xff0c;还是…...

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Refresh)

可以进行页面下拉操作并显示刷新动效的容器组件。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 支持单个子组件。 从API version 11开始&#xff0c;Refresh子组件会跟随手势下拉而下移…...

数据资产管理解决方案:构建高效、安全的数据生态体系

在数字化时代&#xff0c;数据已成为企业最重要的资产之一。然而&#xff0c;如何有效管理和利用这些数据资产&#xff0c;却是许多企业面临的难题。本文将详细介绍数据资产管理解决方案&#xff0c;帮助企业构建高效、安全的数据生态体系。 一、引言 在信息化浪潮的推动下&a…...

Visual Studio 2013 - 调试模式下查看监视窗口

Visual Studio 2013 - 调试模式下查看监视窗口 1. 监视窗口References 1. 监视窗口 Ctrl Alt W&#xff0c;1-4&#xff1a;监视窗口 (数字键不能使用小键盘) or 调试 -> 窗口 -> 监视 -> 监视 1-4 调试状态下使用&#xff1a; 在窗口中点击空白行&#xff0c;…...

CTF 题型 SSRF攻击例题总结

CTF 题型 SSRF攻击&例题总结 文章目录 CTF 题型 SSRF攻击&例题总结Server-side Request Forgery 服务端请求伪造SSRF的利用面1 任意文件读取 前提是知道要读取的文件名2 探测内网资源3 使用gopher协议扩展攻击面Gopher协议 &#xff08;注意是70端口&#xff09;python…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...