Java核心技术卷一 -第十二章:多线程
系列文章目录
Java核心技术卷一 -第一章:java“白皮书”的关键术语
Java核心技术卷一 -第三章:数据类型
Java核心技术卷一 -第三章:变量与常量
Java核心技术卷一 -第三章:运算符
Java核心技术卷一 -第三章:字符串
Java核心技术卷一 -第三章:输入与输出
Java核心技术卷一 -第三章:数组
Java核心技术卷一 -第四章:类之间的关系-依赖
Java核心技术卷一 -第四章:预定义类-LocalDate类小应用
Java核心技术卷一 -第四章:构造器
Java核心技术卷一 -第四章:null引用
Java核心技术卷一 -第四章:方法参数
Java核心技术卷一 -第四章:对象构造
Java核心技术卷一 -第五章:覆盖方法与super
Java核心技术卷一 -第五章:super关键字
Java核心技术卷一 -第五章:类的强制类型转换与instanceof操作符
Java核心技术卷一 -第五章:抽象类与abstract关键字
Java核心技术卷一 -第五章:Object类的toString方法
Java核心技术卷一 -第五章:数组列表初识
Java核心技术卷一 -第五章:装箱和拆箱
Java核心技术卷一 -第五章:枚举类再认识
Java核心技术卷一 -第七章:异常
Java核心技术卷一 -第九章:集合
Java核心技术卷一 -第六章:抽象类和接口
Java核心技术卷一 -第十章:IO流
文章目录
- 系列文章目录
- 前言
- 一、多线程
- 1.1、什么是进程?什么是线程?
- 1.2、基本线程数量
- 1.3、进程和线程是什么关系?
- 举个例子:
- 注意:
- 1.4、思考一个问题:
- 1.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
- 1.6、java语言中,实现线程有两种方式,那两种方式呢?
- 第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
- 代码展示:
- 第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
- 代码展示:
- 1.7、线程的生命周期
- 1.8、线程的方法
- 代码展示:
- 代码展示(在java中怎么强行终止一个线程的执行):
- 直接终止:
- 间接终止:这种方法安全
- 1.9、线程安全
- 为什么这个是重点?
- 什么时候数据在多线程并发的环境下会存在安全问题呢?
- 怎么解决线程安全问题呢?
- 说到线程同步这块,涉及到这两个专业术语:
- 异步编程模型:
- 同步编程模型:
- 1.10、模拟俩个线程对同一账号取款
- Account类:
- AccountThread类:
- Test类:
- 代码执行原理(锁池):
- 提示:
- 小总结:
- 1.11、死锁
- 图示:T1从上往下,T2从下往上,T1锁住第一个后再锁第二个;T2锁住第二个后再锁第一个,此时双方都锁住了要锁的第一个,而要锁的第二个被对方锁住,从而造成死锁。
- 代码展示:
- 我们以后开发中应该怎么解决线程安全问题?
- 1.12、守护线程:
- 图示:
- 代码展示:
- 1.13、定时器:
- 了解:
- 定时器的作用:
- 代码展示:
- 1.14、关于Object类中的wait和notify方法。(生产者和消费者模式!)
- 图示:
- (生产者和消费者模式!):
- 代码展示:
- 总结
前言
本人为java初学者,文章内容仅为个人学习总结分享,其中包含了大量Java核心技术卷一里面的文章内容以及一些网站文章内容,由于参考文章过多,在此并不一一列出,如有侵权,请联系删除。
一、多线程
1.1、什么是进程?什么是线程?
进程是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程。
1.2、基本线程数量
对于java程序来说,当在DOS命令窗口中输入:java HelloWorld回车之后。会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
1.3、进程和线程是什么关系?
举个例子:
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
…
京东:进程
强东:京东的一个线程
妹妹:京东的一个线程
进程可以看做是现实生活当中的公司,
线程可以看做是公司当中的某个员工。
注意:
进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
火车站,可以看做是一个进程:
火车站中的每一个售票窗口可以看做是一个线程。
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
1.4、思考一个问题:
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
1.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于
CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情
同时在做!!!!!
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
给我们的感觉是同时并发的。
电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,
人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像
一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在
这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。
1.6、java语言中,实现线程有两种方式,那两种方式呢?
java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
// 定义线程类public class MyThread extends Thread{public void run(){}}// 创建线程对象MyThread t = new MyThread();// 启动线程。t.start();
代码展示:
public class Test02 {public static void main(String[] args) {AZX azx=new AZX();//启动线程//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了。//启动成功的线程会自动调用run方法,并且run方在分支的栈底部(压栈)。//run方法在分支的底部,main方法在主栈的栈底部。run和main是平级的。azx.start();for (int i=0;i<1000;i++){System.out.println("主线程------>"+i);}}
}class AZX extends Thread{@Overridepublic void run() {for (int i=0;i<1000;i++){System.out.println("分支线程------>"+i);}}
}
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
// 定义一个可运行的类public class MyRunnable implements Runnable {public void run(){}}// 创建线程对象Thread t = new Thread(new MyRunnable());// 启动线程t.start();
代码展示:
public class Test02 {public static void main(String[] args) {//创建一个可运行的对象MyRunnable r=new MyRunnable();//将可运行的对象封装成一个线程对象Thread t=new Thread(r);//启动线程t.start();for (int i=0;i<100;i++){System.out.println("主线程------>"+i);}}
}class MyRunnable implements Runnable{@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println("分支线程------>"+i);}}
}
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承
其它的类,更灵活。
1.7、线程的生命周期
1.8、线程的方法
代码展示:
public class Test02 {public static void main(String[] args) {//让当前线程进行休眠,5秒后唤醒try {//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}//5秒后执行这里的代码System.out.println("HelloWorld!!!");//获取当前对象线程的名字Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName());//创建一个线程对象AZX r=new AZX();//设置线程的名字r.setName("ttttt");//获取线程的名字String tName= r.getName();System.out.println(tName);//启动线程r.start();for (int i=0;i<10;i++){System.out.println("主线程------>"+i);}}
}class AZX extends Thread{@Overridepublic void run() {for (int i=0;i<10;i++){//currentThread就是当前线程对象。当前线程是谁呢?//当t1我程执行run方法,那么这个当前线程就是t1//当t2线程执行run方法,那么这个当前线程就是t2Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName()+":分支线程------>"+i);}}
}
代码展示(在java中怎么强行终止一个线程的执行):
直接终止:
这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。
public class Test02 {public static void main(String[] args) {Thread t=new Thread(new AZX());t.setName("t");t.start();//模拟5秒try {//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}//5秒后强行终止t线程t.stop();//已过时,不建议使用}
}class AZX extends Thread{@Overridepublic void run() {for (int i=0;i<10;i++){//执行十秒Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName()+":分支线程------>"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果:
间接终止:这种方法安全
public class Test02 {public static void main(String[] args) {MyRunnable r=new MyRunnable();Thread t=new Thread(r);t.setName("t");t.start();//模拟5秒try {//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}//5秒后强行终止t线程r.run=false;}
}class MyRunnable implements Runnable{//打一个布尔标记boolean run=true;@Overridepublic void run() {for (int i=0;i<10;i++){if(run){//执行十秒Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName()+":分支线程------>"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}else {//终止当前线程return;}}}
}
结果:
1.9、线程安全
为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:*****)
什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。
用排队执行解决线程安全问题。这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
说到线程同步这块,涉及到这两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高。)
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低:线程排队执行
同步就是排队。
1.10、模拟俩个线程对同一账号取款
Account类:
public class Account {//账户private String actno;//余额private double balance;//构造方法public Account(){}public Account(String actno, double balance) {this.actno = actno;this.balance = balance;}//set与get方法public String getActno() {return actno;}public void setActno(String actno) {this.actno = actno;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}//取款的方法public void withdraw(double money){synchronized (this){//取款之前的余额double before=this.getBalance();//取款之后的余额double after=before-money;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//更新余额this.setBalance(after);}}
}
AccountThread类:
public class AccountThread extends Thread{//俩个线程共享同一个账户对象private Account act;//构造方法public AccountThread(Account act) {this.act = act;}public void run(){//run方法的执行表示取款操作double money=5000;//取款act.withdraw(money);System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额:"+act.getBalance());}
}
Test类:
public class AccountTest01 {public static void main(String[] args) {//创建账户对象(只创建1个)Account act=new Account("act-001",10000);//创建俩个线程Thread t1=new AccountThread(act);Thread t2=new AccountThread(act);//设置namet1.setName("t1");t2.setName("t2");//启动线程取款t1.start();t2.start();}
}
结果:
代码执行原理(锁池):
提示:
这里需要注章的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
小总结:
1.11、死锁
图示:T1从上往下,T2从下往上,T1锁住第一个后再锁第二个;T2锁住第二个后再锁第一个,此时双方都锁住了要锁的第一个,而要锁的第二个被对方锁住,从而造成死锁。
代码展示:
public class DeadLock {public static void main(String[] args) {Object o1=new Object();Object o2=new Object();//t1和t2俩个线程共享o1、o2Thread t1=new MyThread1(o1,o2);Thread t2=new MyThread22(o1,o2);t1.start();t2.start();}
}class MyThread1 extends Thread{Object o1;Object o2;public MyThread1(Object o1, Object o2) {this.o1 = o1;this.o2 = o2;}public void run(){synchronized (o1){try {System.out.println("MyThead1-begin");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2){System.out.println("MyThead1-end");}}}
}class MyThread22 extends Thread{Object o1;Object o2;public MyThread22(Object o1, Object o2) {this.o1 = o1;this.o2 = o2;}public void run(){synchronized (o2){try {System.out.println("MyThead22-begin");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o1){System.out.println("MyThead22-end");}}}
}
结果:卡住了
我们以后开发中应该怎么解决线程安全问题?
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
…
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
…
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
1.12、守护线程:
图示:
代码展示:
package Package03;
public class ThreadTest04 {public static void main(String[] args) {Thread t=new BakDataThread();//启动线程之前,将线程设置为守护线程t.setDaemon(true);//启动线程t.start();//主线程for (int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"--->"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
class BakDataThread extends Thread{public void run(){int i=0;while (true){System.out.println(Thread.currentThread().getName()+"--->"+(++i));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
结果:主线程即main线程结束,守护线程也结束(其死循环结束)
1.13、定时器:
了解:
定时器:Timer类:
注意schedule类:
定时器的作用:
间隔特定的时间,执行特定的程序。
列如:每周要进行银行账户的总账操作、每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
代码展示:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;public class TimerTest {public static void main(String[] args) throws Exception {//创建定时器对象Timer timer=new Timer();//指定定时任务SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");Date firstTime=sdf.parse("2022-02-14 11:15:00");timer.schedule(new LogTimerTask(), firstTime, 1000*10);}
}//编写一个定时任务类
class LogTimerTask extends TimerTask{public void run(){SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");String strTime=sdf.format(new Date());System.out.println(strTime+":成功完成了一次数据备份!");}
}
结果:
1.14、关于Object类中的wait和notify方法。(生产者和消费者模式!)
图示:
(生产者和消费者模式!):
代码展示:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;public class ThreadTest01 {public static void main(String[] args) throws Exception{//创建1个仓库对象,共享的List list=new ArrayList();//创建俩个线程对象//生产者线程Thread t1=new Thread(new Producer(list));//消费者线程Thread t2=new Thread(new Consumer(list));t1.setName("生产者线程");t2.setName("消费者线程");//启动线程t1.start();t2.start();}
}//生产线程
class Producer implements Runnable{//仓库private List list;//数组大小private int size=0;//构造方法public Producer(List list) {this.list = list;}@Overridepublic void run() {//一直生产(使用死循环来模拟一直生产)while (true){//给仓库对象List加锁synchronized (list){if (list.size()>0){try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}//程序能够执行到这里说明仓库是空的,可以生产Object obj=new Object();//获取数组大小for(int i = 0 ; i < list.size() ; i++) {size=i+1;}obj=size;list.add(obj);System.out.println(Thread.currentThread().getName()+"--->"+obj);//唤醒消费者进行消费list.notify();}}}
}//消费线程
class Consumer implements Runnable{//仓库private List list;//数组大小private int size=0;//构造方法public Consumer(List list) {this.list = list;}@Overridepublic void run() {//一直消费while (true){//给仓库对象List加锁synchronized (list){if (list.size()==0){try {//仓库已经空了//消费者线程等待list.wait();} catch (InterruptedException e) {e.printStackTrace();}}//程序能够执行到这里说明仓库是有数的,可以消费//获取数组大小for(int i = 0 ; i < list.size() ; i++) {size=i;}Object obj=list.remove(size);System.out.println(Thread.currentThread().getName()+"--->"+obj);//唤醒生产者进行消费list.notify();}}}
}
结果:(生产和消费交替运行)
总结
以上就是本文的内容,记录了一些关于java“多线程”的内容,本人也是刚开始接触java,不能保证总结内容的正确性,若是有错误的话,欢迎大家指出,谢谢!
Java核心技术卷一 -第十二章:多线程相关推荐
- 【JAVA SE】第十二章 流(Stream)、IO流和文件(File)
第十二章 流(Stream).IO和文件(File) 文章目录 第十二章 流(Stream).IO和文件(File) 一.流(Stream) 1.什么是流 2.流的分类 二.IO流 1.字节输入流(I ...
- 第十二章 多线程基础
文章目录 12.1 进程与线程 12.2 创建线程 12.2.1 继承Thread类 12.2.2 实现Runnable接口 12.2.3 区别和联系 12.3 终止线程 12.4 线程常用方法 12 ...
- 《Java核心技术卷一》第3章— — —个人知识点整理
1 关键字public称为访问修饰符,它用于控制程序的其他部分对这段代码的访问级别 2 关键字class表明Java程序中的全部部分都包含在类中,将类作为一个加载程序逻辑的容器,程序逻辑定义了应用程序 ...
- Java课后题第十二章:12.18(添加包语句)
1.找到文件路径 File file = new File("D:\\PackageP13\\");File[] files = file.listFiles(); 2.复制到St ...
- Java核心技术卷一读书笔记
文章目录 Java核心技术卷一读书笔记 第一章 Java程序设计概述 1.1 关键特性 第二章 Java程序设计环境 2.1 使用命令行工具 第三章 Java的基本查询设计结构 3.1 数据类型 3. ...
- 《Kotlin 程序设计》第十二章 Kotlin的多线程:协程(Coroutines)
第十二章 Kotlin的多线程:协程(Coroutines) Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, ...
- Java核心技术卷一 -第九章:集合
系列文章目录 Java核心技术卷一 -第一章:java"白皮书"的关键术语 Java核心技术卷一 -第三章:数据类型 Java核心技术卷一 -第三章:变量与常量 Java核心技术卷 ...
- Java核心技术卷一 -第四章:方法参数
系列文章目录 Java核心技术卷一 -第一章:java"白皮书"的关键术语 Java核心技术卷一 -第三章:数据类型 Java核心技术卷一 -第三章:变量与常量 Java核心技术卷 ...
- Java核心技术卷一、二读书笔记(PDF)分享
分享一下笔记(书)PDF在下面,懂得都懂 一 Java程序设计概述 1)Java语言的特性 简单性 Java语法是c++的一个纯净版本,这里没有头文件,指针运算(指针语法),结构,联合,操作符重载,虚 ...
最新文章
- java+实现集合并运算_JAVA程序设计报告+集合运算
- 跟着实例学习设计模式(7)-原型模式prototype(创建型)
- Android Svn 中 Bin ,Gen 目录不进行版本控制
- 汇编学习笔记(4)-伪指令(MASM)
- 【渝粤教育】广东开放大学 会展项目管理 形成性考核 (59)
- “Device eth0 does not seem to be present”解决办法
- Swagger2 生成API文档时泛型总是显示不出来的问题
- 解决2010版办公软件打不开从系统中导出的Excel表格
- 完美解决Python与anaconda之间的冲突问题
- zynq以太网官网例子调试
- 计算机快速看图教程,cad快速看图制图
- Ravpower苹果20W充电器,充电快又稳,使用更安全
- VMware系统启动假死,一直处于“繁忙”状态
- c++ 一个学习小组有5个人,每个人有三门课(高数、英语和C语言)的考试成绩,求每人的平均成绩。按行输出每个学生的各科成绩及平均成绩。
- JQ...CSS3爱心飘落特效
- 实用经验 92 区分函数模版与模版函数,类模版和模板类
- 串行进位加法器和超前进位加法器代码实现及性能对比
- 恢复计算机到手机桌面,桌面的图标怎么恢复原来的呀?
- NUCLEO-F401RE
- 对抗抑郁症带来的悲伤,治愈系AI心理医生会有用吗?