尚硅谷视频总结——Java多线程

article/2025/5/10 9:57:36

多线程

一:基本概念:程序,进程,线程

程序(program):程序是为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程(process):进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程:有它自身的产生,存在,消亡的过程。——生命周期。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。

线程(thread):进程可进一步细分为多个线程,是一个程序内部执行的一条路径。

  • 若一个程序同一时间并行执行多个线程,则称该程序是支持多线程的
  • 线程作为调度和执行的,每个线程拥有独立的运行栈和程序计数器(PC)
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间——堆和方法区是一个进程所拥有的,线程可以访问其中的对象和变量。这使得线程通信更加简便,有效,但同时多个线程共享的系统资源可能会带来安全隐患

多线程程序的优点:

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率。
  3. 改善程序结构,将既长又复杂的进程分为多个线程,独立运行,有利于理解和修改。

何时需要多线程:

  1. 程序需要同时执行两个或多个任务
  2. 程序需要实现一些等待的任务时,如用户输入,文件读写操作,网络操作等
  3. 需要一些后台运行的程序

多线程的创建

1. 继承自Thread类

  1. 创建一个类继承自Thread类
  2. 重写Thread类的run()方法,run()方法中包含该线程需要执行的逻辑
  3. new一个Thread类的子类的对象
  4. 通过调用该对象的start()方法启动线程
class ChildThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println("白");}}}
}
public class MyThread {public static void main(String[] args) {ChildThread t1 = new ChildThread();// start()方法有两个作用,启动线程,调用该线程的run方法// 若调用t1.run(),那么没有启动子线程,只是调用了对象的run()方法。// 一个线程对象只能start()一次t1.start();for (int i = 0; i < 100; i++) {if(i % 2 != 0) {System.out.println("梦" + "Main");}}}
}

Thread类中的常用方法:

  • start():启动当前线程,并调用线程的run()方法
  • run():通常需要重写Thread类中的run()方法,将创建的线程的执行逻辑写在此方法中
  • currentThread:静态方法,获得当前正在执行的线程
  • getName:返回当前正在执行线程的名字
  • setName:设置当前线程的名字
  • yield():释放当前CPU的执行权
  • join():在线程a中执行线程b的join()方法,此时线程a进入阻塞状态,直到线程b执行完之后,线程a才结束阻塞状态
  • sleep(Long millitime):让当前线程睡眠(阻塞)millitime毫秒
  • isAlive:判断当前线程是否存活

线程的优先级:

  1. 优先级

    • MIN_PRIORITY = 1
      
    • NORM_PRIORITY = 5
      
    • MAX_PRIORITY = 10
      
  2. 获得和设置优先级

    public final int getPriority() {return priority;}
    
    public final void setPriority(int newPriority)
    

高优先级的线程可以抢占低优先级线程的资源,但并不绝对,只是说它拥有这个能力。

卖票实例(具有隐患):

// 多个线程共享资源带来了一定的安全隐患,后续解决class WindowDemo extends Thread{// ticket变量需声明为static,需要多个窗口(对象)共用一个变量private static int ticket = 100;@Overridepublic void run() {while (true){if(ticket > 0){System.out.println(getName() + ":" + "卖出票" + ticket);ticket--;}else{break;}}}
}
public class SellTicketDemo {public static void main(String[] args) {WindowDemo w1 = new WindowDemo();WindowDemo w2 = new WindowDemo();WindowDemo w3 = new WindowDemo();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}

2.实现Runnable接口的类

  1. 创建一个实现Runnable接口的类
  2. 实现Runnable接口中的方法(run()方法)
  3. 创建一个该类的对象
  4. 将此对象作为参数传入到Thread类的构造器中,创建一个Thread对象
  5. 通过调用Thread对象的start方法,开启线程
// 同理,该类存在线程安全问题
class WindowDemo2 implements Runnable{private int ticket = 100;@Overridepublic void run() {while (true){if(ticket > 0){// 该类非Thread类的子类,所以需要调用完整的getName()方法System.out.println(Thread.currentThread().getName() + ":" + "卖出票" + ticket);ticket--;}else{break;}}}
}
public class SellTicketDemo2 {public static void main(String[] args) {WindowDemo2 windowDemo2 = new WindowDemo2();// 这里不需要将ticket声明为static,因为是使用同一个对象去创建的线程,三个线程访问同一个ticketThread t1 = new Thread(windowDemo2);t1.setName("窗口1");Thread t2 = new Thread(windowDemo2);t2.setName("窗口2");Thread t3 = new Thread(windowDemo2);t3.setName("窗口3");/*调用Thread类的start方法,而start方法又会调用Thread类的run(),为什么这里他去调用了实现*Runnable接口类的run()方法呢。因为Thread类的start()方法会先判断是否传入实现Runnable接口*的类,若是的话,则调用实现Runnable接口类的run()方法 */t1.start();t2.start();t3.start();}
}

比较创建多线程的两种方式:

  1. 开发中,优先选择使用第二种方式,实现Runnable接口的方式
    1. 实现接口的方式可以摆脱Java中类的继承的局限性
    2. 在需要开启多个线程,且这些线程拥有共享数据的时候,实现接口的方式更适合
  2. 实际上,Thread类就是实现Runnable接口的类,我们创建一个类去继承Thread类,并覆盖它的run()方法,归根结底还是实现Runnable接口中的run()方法

3. JDK5.0新增的两种方式

3.1 实现Callable接口的类

与Runnable相比,Callable的功能更加强大

  • call方法可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

实现步骤:

  1. 创建一个实现Callable接口的实现类
  2. 实现类重写call方法,将此线程需要执行的逻辑写在call()方法中
  3. 创建实现类的一个对象
  4. 将实现类的对象作为参数传入到FutureTask的构造器中,创建一个FutureTask对象
  5. 将FutureTask对象作为参数传入到Thread类的构造器中,创建一个Thread对象,并调用start()方法
class ThreadTest implements Callable<Integer>{@Overridepublic Integer call() throws Exception {Integer sum = 0;for (int i = 1; i <= 100; i++) {if(i % 2 == 0){System.out.println("遍历到了" + i);sum += i;}}System.out.println("和为:" + sum);return sum;}
}
public class CallableDemo {public static void main(String[] args) {ThreadTest threadTest = new ThreadTest();// Future接口的唯一实现类,实现了Runnable接口和Callable接口FutureTask<Integer> integerFutureTask = new FutureTask<>(threadTest);new Thread(integerFutureTask).start();try {Integer ans = integerFutureTask.get();System.out.println(ans);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}

3.2 使用线程池

思路:提前创建好多个线程放入线程池,使用时直接获取,使用完又放回到池中。这样可以避免线程多次创建销毁,进而实现重复利用。

好处:

  • 提高响应速度(减少创建新线程的时间)

  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)

  • 便于线程管理

    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • KeepAliveTime:线程没有任务时最多保持多久会终止

线程的生命周期

Thread.State类中定义了线程生命周期中的5个状态:

  • 当一个Thread类或它的子类对象被生命并创建时,新生的线程对象处于新建状态(新建)
  • 处于新建状态的线程调用start方法后,将进入线程队列,等待CPU时间片,此时它已经具备了运行的条件,只是还没被CPU分配内存资源(就绪)
  • 当就绪的线程被被调度并且获得CPU资源时,便进入了运行状态,run()方法中定义了线程的操作和功能(运行)
  • 在某种特殊情况下,被人为挂起或执行输入输出时,让出CPU并临时终止自己的执行,进入阻塞状态(阻塞)
  • 线程完成了它的全部功能或线程被强制地终止或出现异常导致线程结束(死亡)

在这里插入图片描述

多线程的安全问题:

  1. 多个线程执行的不确定性引起执行结果的不稳定
  2. 多个线程对数据的共享,可能会对数据造成破坏

解决方法

解决:当一个线程在操作共享数据时,其它的线程不能够参与进来,直到该线程结束后,才允许其它线程对共享数据的操作。

在Java中,我们通过同步机制来解决线程安全的问题。

**方法一:**同步代码块

synchronized(同步监视器){

// 需要被同步的代码,(操作共享数据的代码),共享数据为多个线程共同操作的变量。

}

同步监视器俗称“锁”,任意一个对象即可。但是所有的存在安全隐患线程需要共同使用一个对象,即它们共同拥有一把锁

**方法二:**同步方法

如果同步代码块被完全声明在一个方法中,我们可以将这个方法声明为同步的。

声明同步方法的方式是在方法的返回值前面加上synchronized关键字。

同步方法仍然会用到同步监视器,只不过不需要被主动的声明。

非静态的同步方法,同步监视器为this。该情况对应的是以Runnable接口的形式创建线程

静态的同步方法,同步监视器为当前类本身。该情况对应的是以Thread类子类的形式创建线程

同步的方式解决了线程安全的问题,但同时每次只能够有一个线程执行同步代码块,相当于又回到了单线程的过程(局限性)。

单例模式之懒汉式的线程安全

1. 什么是单例模式

单例模式是保证整个应用程序周期内,在任何时刻,被指定的类只能够有一个实例。

实现单例模式的方式:

  1. 构造方法声明为私有的,外界不能够调用构造方法构造对象
  2. 类本身需要构造一个对象——调用构造方法即可
  3. 通过公共的方法对外提供这个唯一的实例对象

2. 实例

// 外界只能通过getInstance()方法来获得Bank类的唯一对象
class Bank{private Bank(){};private static Bank instance = null;public static Bank getInstance(){// 该层if语句可以提高一定的效率, 因为同步操作实际上只需要进入的第一个// 线程操作。if(instance == null){// 同步代码块的方式synchronized(Bank.class){if(instance == null){instance = new Bank();}}}return instance;}
}

线程的死锁问题

  • 死锁的理解
    • 不同的线程占据着对方所需要的同步监听器不放弃,都在等待着对方所占据的自己所需要的同步监听器,进而形成了线程的死锁。
    • 死锁出现后,程序并不会抛出异常,也不会出现提示,只是线程此时处于阻塞状态,无法继续。这不符合我们对于线程的定义,所有的线程最后都应该消亡。
  • 解决办法
    • 专门的算法,原则
    • 尽量减少同步资源的定义
    • 尽量避免嵌套同步

死锁示例:

public class DeadLock {public static void main(String[] args) {StringBuilder s1 = new StringBuilder();StringBuilder s2 = new StringBuilder();new Thread(){@Overridepublic void run() {synchronized(s1){s1.append('a');s2.append(1);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2){s1.append('b');s2.append(2);}}System.out.println(s1);System.out.println(s2);}}.start();new Thread(() -> {synchronized (s2){s1.append('c');s2.append(3);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s1){s1.append('d');s2.append(4);}}System.out.println(s1);System.out.println(s2);}).start();}
}

Lock锁

Lock锁是JDK5.0新增的解决线程安全的方法。同时Lock是一个接口,我们一般使用它的实现类ReentrantLock类。 // 它自己就是一把抽象的锁。

class WindowDemo extends Thread{// ticket变量需声明为static,需要多个窗口(对象)共用一个变量// Lock锁同样要声明为静态的private static ReentrantLock lock = new ReentrantLock(true);private static int ticket = 100;@Overridepublic void run() {while (true) {lock.lock();try {if (ticket > 0) {System.out.println(getName() + ":" + "卖出票" + ticket);ticket--;} else {break;}}finally {lock.unlock();}}}

synchronized与Lock的区别:

  • synchronized的机制是在执行完同步代码块或方法后,自动释放同步监视器。

  • Lock需要手动的开启同步(Lock.lock()),同时在同步结束之后需要手动的结束结束同步(Lock.unlock())。

线程的通信

线程通信涉及到的三个方法:

  • wait():执行此方法,当前线程将进入到阻塞状态,并且释放同步监视器。
  • notify():执行此方法,将唤醒被wait()的一个线程,如果有多个线程被wait()则唤醒优先级高的那个线程。
  • notifyAll():执行此方法,将唤醒所以被wait()的线程。

注意:

  • 以上三个方法必须使用在同步代码块或同步方法中
  • 以上三个方法的调用者必须是同步代码块或者同步方法的同步监听者
  • 以上三个方法定在在java.lang.Object类中

sleep()方法和wait()方法的异同:

相同点:一旦执行上述方法,当前正在进行的线程将进入阻塞状态。

不同点:1)sleep()方法声明在Thread类中,为静态方法。wait()方法声明在Object()类中。

​ 2)调用的要求不同,只要我们想要,随时可以通过Thread.sleep()调用sleep()方法。而wait()方法必须在 同步方法或同步代码块中调用。

​ 3)调用wait()方法会释放同步监视器,而调用sleep()方法不会释放同步监视器。

生产者与消费者问题

有一个生产者可以一直生产产品,柜台工作人员可以从生产者这里得到产品,而消费者可以从柜台工作人员这里买到产品。要求唱片最多为66个,一旦有了66个产品,柜台工作人员就会通知生产者不要生产了,同时当没有产品得时候,柜台工作人员会告述消费者不要来买东西了,没有了。

分析:

  • 多线程问题,生产者是一类线程,消费者是一类线程。
  • 存在线程安全问题,它们之间有共享数据,柜台工作人员或者说是柜台工作人员手中的产品
/** Clerk类表示柜台工作人员,成员变量productNumber表示当前产品的个数。* 生产者可以到调用produce()方法表示生产一个产品* 消费者可以调用consume()方法表示买走一个产品* 生产者和消费者需要调用getProductNumber()方法才能得得当前的产品个数*/
class Clerk{private int productNumber = 0;public int getProductNumber() {return productNumber;}public void produce() {productNumber++;}public void consume() {productNumber--;}
}
class Producer implements Runnable{// 声明成员变量clerk,它是多线程的共同变量,可充当同步锁使用private Clerk clerk;// 声明构造方法从外部传入公共的clerkpublic Producer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {while (true) {synchronized (clerk) {if (clerk.getProductNumber() < 66) {// notify方法与wait方法的调用对象应该和同步锁一样,若不声明由谁调用方法,将会默认为this.方法clerk.notify();clerk.produce();System.out.println(Thread.currentThread().getName() + "生产产品:" + clerk.getProductNumber());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}} else {try {clerk.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
class Customer implements Runnable{private Clerk clerk;public Customer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {while (true) {synchronized (clerk) {if (clerk.getProductNumber() > 0) {// notify方法与wait方法的调用对象应该和同步锁一样,若不声明由谁调用方法,将会默认为this.方法clerk.notify();System.out.println(Thread.currentThread().getName() + "消费产品:" + clerk.getProductNumber());clerk.consume();try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}} else {try {clerk.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
public class ThreadDemo {public static void main(String[] args) {Clerk clerk = new Clerk();Producer producer = new Producer(clerk);Customer customer = new Customer(clerk);Customer customer1 = new Customer(clerk);Thread t1 = new Thread(producer);Thread t2 = new Thread(customer);Thread t3 = new Thread(customer1);t1.setName("生产者1");t2.setName("消费者1");t3.setName("消费者2");t1.start();t2.start();t3.start();}
}

http://chatgpt.dhexx.cn/article/QRBECIGv.shtml

相关文章

Vue学习笔记--第一章(尚硅谷学习视频总结)

目录 一、第一章 Vue核心 1.1. Vue简介 1.1.1. 官网 1.1.2. 介绍与描述 1.1.3. Vue 的特点 1.1.4. 与其它 JS 框架的关联 1.1.5. Vue 周边库 1.2.初识Vue 1.3. 模板语法 1.4. 数据绑定 1.5 el与data的两种写法 1.6 MVVM模型 1.7 Vue中的数据代理 1.8.事件处理 1.…

JUC编程的使用(基于尚硅谷视频)

文章目录 1.核心概念2.线程2.1线程的实现2.2 多线程的使用方法&#xff08;重点&#xff09;2.3.Synchronized关键字&#xff08;自动上锁和解锁&#xff09;2.4Look锁&#xff08;手动上锁和解锁&#xff09;2.4.1常用方法2.4.2常用类 2.5可定制化线程 2.6线程的状态3.ArrayLi…

强强联手 | 尚硅谷Dinky视频教程发布

Dinky为Apache Flink而生&#xff0c;让Flink SQL纵享丝滑。顾名思义&#xff0c;Dinky是一个小而美的框架&#xff0c;它基于 Apache Flink 二次开发&#xff0c;无侵入&#xff0c;开箱即用&#xff0c;是易扩展的一站式 FlinkSQL & SQL DataOps&#xff0c;可以很大程度…

尚硅谷视频讲解:多态性

1.理解多态性 具体来讲&#xff0c;是对象的多种形态&#xff0c;可以是man&#xff0c;woman等 父类的引用即p2 子类的对象即 Man&#xff1b; 声明的是变量 Person p2 右边是对象&#xff08;man&#xff0c;或者woman&#xff0c;只要是person其他子类都可以&#xff09;…

Vue.js尚硅谷视频学习笔记(第一章:Vue 核心)

第1 章&#xff1a;Vue 核心 1.1. Vue 的基本认识 1.1.1. 官网 英文官网: https://vuejs.org/中文官网: https://cn.vuejs.org/ 1.1.2. 介绍描述 渐进式JavaScript 框架作者: 尤雨溪(一位华裔前Google 工程师)作用: 动态构建用户界面 1.1.3. Vue 的特点 遵循MVVM 模式编…

尚硅谷Kylin视频教程发布!

Atlas视频教程发布后&#xff0c;有谷粉说&#xff1a; 就这&#xff1f;我两天就学完了。 垒哥小浣熊脸&#xff0c;不服又很无奈&#xff1a; 人生路还很长&#xff0c;年轻人别太狂。 我们尚硅谷的大数据学科&#xff0c; 不能给生产队的驴和老母猪丢脸&#xff01; 来…

「尚硅谷与腾讯云官方合作」硅谷课堂项目视频发布

硅谷课堂是尚硅谷与腾讯云官方合作的项目&#xff0c;是一款基于微信公众号B2C模式的在线学习平台。项目包含后台系统和微信公众号部分&#xff0c;采用前后端分离开发模式。 本套项目教程针对有一定开发经验的Java程序员精心打造&#xff0c;项目技术涵盖微服务、微信公众号、…

javascript全笔记-基础版(尚硅谷视频李立超老师)

目录 一.JavaScript简介 1.1 JS简介 1.2 JS的HelloWorld 1.3 JS代码编写的位置 二.JS的基础语法 2.1 JS的基本语法 2.2 字面量与变量 2.3 标识符 2.4 数据类型 2.4.1 typeof运算符 2.4.2 String 2.4.3 Number 2.4.4 Boolean 2.4.5Null 2.4.6Undefined 2.5 强制…

尚硅谷前端视频总结(二)

尚硅谷前端视频总结&#xff08;二&#xff09; 原文链接 动画animation CSS animation 属性是 animation-name&#xff0c;animation-duration, animation-timing-function&#xff0c;animation-delay&#xff0c;animation-iteration-count&#xff0c;animation-directi…

Spring Cloud 尚硅谷阳哥学习笔记,每一行代码均有解释,适合快速上手,并配合尚硅谷视频食用

Spring Cloud ATenOne ❤️ 一、正常 SpringBoot 环境的测试 1、版本的选择 SpringCloud Hoxton.SR1SpringBoot 2.2.2.RELEASESpringCloud Alibaba 2.1.0.RELEASEJava 8Maven 3.5Mysql 8.0.25 父 pom.xml 如下 <?xml version"1.0" encoding"UTF-8&quo…

vue3快速上手(尚硅谷视频笔记)

Vue3快速上手 1.Vue3简介 2020年9月18日&#xff0c;Vue.js发布3.0版本&#xff0c;代号&#xff1a;One Piece&#xff08;海贼王&#xff09;耗时2年多、2600次提交、30个RFC、600次PR、99位贡献者github上的tags地址&#xff1a;https://github.com/vuejs/vue-next/releas…

Linux全笔记(尚硅谷视频)

Linux是什么 是一个操作系统&#xff08;OS&#xff09; 李纳斯托瓦兹 Linux 能运行主要的 UNIX 工具软件、应用程序和网络协议。它支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想&#xff0c;是一个性能稳定的多用户网络操作系统。 比较WindowsLinux界…

软件开发介绍-尚硅谷视频学习随记

目录 软件开发相关概念 Java相关介绍 环境配置 常用dos命令 常用快捷键 软件开发相关概念 1.软件&#xff1a;一系列按照特定顺序组织的计算机数据和指令集合&#xff0c;进而构成的一种工具。分为系统软件&#xff08;操作系统&#xff09;和应用软件。 应用程序算法数据…

NodeJs(尚硅谷视频学习笔记)

内容来自尚硅谷Nodejs学习课件以及自己添加 课前预热&#xff1a;CMD基本知识 1.命令行窗口&#xff08;小黑屏&#xff09;、CMD窗口、终端、shell - 开始菜单 --> 运行 --> CMD --> 回车 - Win R --> CMD --> 回车 - 常用的指令dir 列出当前目录下的所…

前端项目-尚品会-来自b站尚硅谷视频

目录 前言gulishop-client---vue2项目目录分页器 前言 重温Vue&#xff0c;打开之前的项目文件夹&#xff0c;陌生又熟悉… 3月份左右看的项目视频&#xff0c;现在已经忘得差不多了…甚至记不清自己是看的哪个视频…刚刚才想起来自己并没有看Vue的视频教程&#xff0c;当初直…

Linux的使用_尚硅谷视频学习笔记

到达底部 文章目录 Linux的使用参考 第 1 章 Linux 开山篇1.1 本套 Linux 课程的内容介绍1.2 Linux 的学习方向1.3 Linux 的应用领域1.3.1个人桌面应用领域1.3.2服务器应用领域1.3.3嵌入式应用领域 1.4 学习 Linux 的阶段&#xff08;高手进阶过程&#xff09;1.5 Linux 的学习…

【javaScript】学完js基础,顺便把js高级语法学了(尚硅谷视频学习笔记)

文章目录 【1】基本总结深入一、什么是数据1、数据类型基本&#xff08;值&#xff09;类型对象&#xff08;引用&#xff09;类型 2、判断相关问题 二、什么是内存1、什么是数据2、什么是内存&#xff1f;3、什么是变量4、内存、数据、变量三者之间的关系相关问题1、问题&…

Mybatis-plus (教程来自尚硅谷视频)

1.什么是Mybatis-plus? 官网地址&#xff1a;MyBatis-Plus 1.1MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 Mybatis-plus的愿景成为Mybatis的最好拍档&a…

JavaSE(尚硅谷视频学习笔记)

文章目录 Java基础编程Java语言概述Java语言简述1.基础图解2.常识3.计算机语言的发展迭代4.Java语言版本迭代概述5. Java语言应用的领域6.Java语言的特点 开发环境的搭建1. JDK、JRE、JVM的关系2. JDK的下载安装 注释与API文档1. 注释Comment2. Java API 文档3. 良好的编程风格…

尚硅谷Java入门视频教程(一)编程入门

冯诺依曼体系结构&#xff1a;中央处理器(CPU)(寄存器、算术逻辑单元、控制单元)、内存(主存)、存储设备(内存、硬盘)、输入输出设(外设、显示器)、通信设备(网卡等)。通过总线连接&#xff0c;传输数据。 中央处理器&#xff1a;(Central Processing Unit CPU)&#xff1a;获…