并发编程笔记7--并发编程基础

1、线程简介

1.1、什么是线程

现代操作系统中运行一个程序,会为他创建一个进程。而每一个进程中又可以创建许多个线程。现代操作系统中线程是最小的调度单元。
两者关系:一个线程只属于一个进程,而一个进程可以拥有多个线程。线程是一个轻量级的进程
一个Java程序从main方法开始执行,然后按照既定的顺序执行代码,看上去没有其他线程参与。但实际上Java程序天生就是多线程程序。我们可以通过JMX来查看一个普通的Java程序包括哪些线程。示例代码如下:

public class MultiThread {public static void main(String[] args) {ThreadMXBean threadMXBean= ManagementFactory.getThreadMXBean();ThreadInfo[] threadInfos=threadMXBean.dumpAllThreads(true,true);for (ThreadInfo threadInfo : threadInfos) {System.out.println(threadInfo.getThreadId()+":"+threadInfo.getThreadName());}}
}

上面代码执行结果如下:
image.png
从上图可以看到,一个Java程序运行不仅是main方法的运行,而是main进程和一些其他进程的同时运行。

1.2、为什么要使用多线程

  1. 更多的处理器核心:现代计算机大多是多核处理器,一个程序作为一个进程在处理器上运行。程序运行过程中可以创建多个线程,而一个线程只能在一个处理器核心上。试想一下,一个单线程程序在运行时只能使用一个处理器核心,那么在多的处理器核心加入也不会提升程序的运行效率。相反如果采用多线程技术,那么运行在多核处理器上就会显著提升运行效率。
  2. 更快的相应时间
  3. 更好的编程模型

**

1.3、线程优先级

在《Java并发编程的艺术》的一书中,作者的实验代码的结果得出的结论是线程的优先级不能作为程序正确性的依赖,操作系统完全可以不用理会Java代码对优先级的设置(作者环境为jdk1.7+Mac OS X10.10)。但是我自己跑出的结果可以看到代码中设置的优先级是有用的(本人环境jdk1.8+win10)。实例代码如下:

public class Priority {private static volatile boolean notStart=true;private static volatile boolean notEnd=true;public static void main(String[] args) throws InterruptedException {List<Job> jobs=new ArrayList<>();for (int i=0;i<10;i++){int priority=i<5?Thread.MIN_PRIORITY:Thread.MAX_PRIORITY;Job job=new Job(priority);jobs.add(job);Thread thread=new Thread(job,"Thread:"+i);thread.setPriority(priority);thread.start();}notStart=false;TimeUnit.SECONDS.sleep(10);notEnd=false;for (Job job:jobs){System.out.println("Job priority:"+job.priority+" count:"+job.jobCount);}}static class Job implements Runnable{private int priority;private long jobCount;Job(int priority){this.priority=priority;}@Overridepublic void run() {while (notStart){Thread.yield();}while (notEnd){Thread.yield();jobCount++;}}}
}

本人跑出的结果为:
image.png

从上图可以看到优先级为10的结果和优先级为1的结果差距是挺大的。至于为什么会和作者的结论不一样,尚未搞清楚。

1.4、线程的状态

线程在运行过程中可能处于以下6种状态中的某一个,在给定的一个时刻,线程只能处于其中的一个状态。

状态名称说明
NEW初始状态,线程被构建,但是还未调用start方法
RUNNABLE运行状态,线程调用start方法后,Java线程将操作系统中的就绪和运行两种状态笼统的成为运行中
BLOCKED阻塞状态,表示线程阻塞与锁
WAITING等待状态,进入该状态的线程需要等待其他线程做出一些特定的动作(通知,中断)
TIME_WAITING超时等待,表示线程等待指定的时间之后自动唤醒,继续执行
TERMINATED终止状态,表示当前线程已经执行完毕

我们可以通过jstack命令来查看线程的执行状态。示例代码如下:

public class ThreadState {//改该程一直睡眠static class TimeWaiting implements Runnable{@Overridepublic void run() {while (true){try {TimeUnit.SECONDS.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}//该线程一直等待static class Waiting implements Runnable{@Overridepublic void run() {while (true){synchronized (Waiting.class){try {Waiting.class.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}static class Blocked implements Runnable{@Overridepublic void run() {synchronized (Blocked.class){while (true){try {TimeUnit.SECONDS.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}}public static void main(String[] args) {new Thread(new TimeWaiting(),"TimeWaitingThread").start();new Thread(new Waiting(),"WaitingThread").start();new Thread(new Blocked(),"BlockedThread-1").start();new Thread(new Blocked(),"BlockedThread-2").start();}
}

1、先通过jps命令来查看线程pid
2、通过jstack pid来查看线程的状态

image.png
image.png
image.png
image.png

java线程变迁图如下:
image.png
从上图可以看到,当线程被创建之后,执行start方法之后线程处于运行状态。当线程执行wait方法之后,线程处于等待状态,这时此线程需要其他线程唤醒才能继续执行(notify和notifyAll)。而超时等待状态是在等待状态的基础上加了等待超时机制,也就是说线程在等待指定的时间后,就会自动回到执行状态,不需要其他线程唤醒。当线程调用同步方法时,在没有获取到锁的情况下,线程会被阻塞住,此时线程处于阻塞状态。当线程执行run方法之后,就处于终止状态了。
阻塞状态是线程阻塞在进入由synchronize修饰的方法或者代码块时的状态。但是阻塞状态在java.concurrent包中的Lock接口上的状态是等待状态。那是因为java.concurrent包中Lock接口对阻塞的实现均使用了LockSupport类中的方法。

1.5、Daemon线程

Daemon线程是一种支持型线程,主要用作程序中后台调度以及支持性工作。在JVM中,当不存在非Daemon线程时,JVM将会退出。
Daemon线程时支持型线程,当JVM退出时,Daemon线程中的finally块并不一定会执行。实例代码如下:

public class Daemon {static class DaemonRunner implements Runnable{@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}finally {System.out.println("DaemonThread finally run.");}}}public static void main(String[] args) {Thread thread=new Thread(new DaemonRunner(),"DaemonThread");thread.setDaemon(true);thread.start();}
}

如果是Daemon线程执行结果如下:
image.png
非Daemon线程执行结果如下:
image.png

PS:在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

2、线程的启动和终止

2.1、线程的启动

线程的启动通过调用线程的start方法来启动。start方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start方法的线程。
PS:启动一个线程前,最好设置线程的名称,这样,在使用jstack排查程序问题时,就会给开发人员提供一些提示。自定义线程最好能够起个别名。

2.2、线程的中断

中断可以理解为线程的一个标识位属性。他表示一个运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否中观来进行响应,线程通过isInterrupted来进行是否中断判断,也可以调用静态方法Thread.interrupted对当前线程的中断标识位进行复位。但是如果线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted时,依旧会返回false。
在Java的许多API中,有许多抛出InterruptedException的方法,这些方法在抛出InterruptedException之前,JVM会先将线程中的中断标识位清除,这是调用该线程对象的isInterrupted时,依旧会返回false。
示例代码如下:

public class Interrupted {static class SleepRunner implements Runnable{@Overridepublic void run() {while (true){try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}}static class BusyRunner implements Runnable{@Overridepublic void run() {while (true){}}}public static void main(String[] args) throws InterruptedException {Thread sleepThread=new Thread(new SleepRunner(),"SleepRunner");
//        sleepThread.setDaemon(true);Thread busyThread=new Thread(new BusyRunner(),"BusyRunner");
//        busyThread.setDaemon(true);sleepThread.start();busyThread.start();TimeUnit.SECONDS.sleep(5);sleepThread.interrupt();busyThread.interrupt();System.out.println("SleepThread interrupted is"+ sleepThread.isInterrupted());System.out.println("BusyThread interrupted is"+ busyThread.isInterrupted());TimeUnit.SECONDS.sleep(2);}
}

运行结果如下:
image.png
从结果中可以看到抛出异常的SleepThread线程在抛出后,其标识位被清楚了返回的是false,而一直运行的BusyThread线程返回是true。

2.3、过期的suspend,resume,stop

1、suspend():暂定线程
2、resume():恢复线程
3、stop():终止线程

过期的原因:以suspend方法为例,在调用后,线程不会释放占用的资源(比如锁),而是占用着资源进行睡眠,这时就有可能导致死锁。同样stop方法在终止线程时,不会保证线程的资源正常释放,通常是没有给予线程完成资源释放的机会。

2.4、安全的终止线程

中断是一种简便的线程间的交互方式,而这种方式最适合用来取消或停止任务。当然还可以用共享变量来终止任务。示例代码如下:

public class Shutdown {static class Runner implements Runnable{private long i;private volatile boolean on =true;@Overridepublic void run() {while (on && !Thread.currentThread().isInterrupted()){i++;}System.out.println(i);}public void cancel(){on=false;}}public static void main(String[] args) throws InterruptedException {Runner one=new Runner();Thread thread=new Thread(one,"one");thread.start();TimeUnit.SECONDS.sleep(1);thread.interrupt();Runner weo=new Runner();Thread thread1=new Thread(weo,"weo");thread1.start();TimeUnit.SECONDS.sleep(1);weo.cancel();}
}

运行结果如下:
image.png
在示例中,main方法中通过中断操作和cancel方法均可使线程终止。这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源而不是武断的将线程终止。

3、线程间的通信

3.1、volatile和synchronize

java允许多线程同时访问同一个共享变量。由于每一个线程都有这个变量的拷贝(JMM协议),所以线程中看到的变量不一定是最新的。
关键字volatile可以用来修饰变量,就是用来告知程序对这个变量的访问要从内存中取,对这个变量的修改要立即写到刷新到内存中,这样其他线程每次看到的都是最新的数据。volatile详情
关键字synchronize可以修饰方法或者代码块,他用来保证同一时刻只能有一个线程能进入方法或者同步代码块中。他保证了线程对变量访问的排他性和可见性。synchronize详情

3.2、等待/通知机制

一个线程修改了一个值,另一个线程感知到变化,然后进行相关的操作。整个过程开始于一个线程,而最终执行与另一个线程。前者是生产者,后者就是消费者。这种结构在功能层面上实现了解耦,体系结构具有良好的伸缩性。那么在java中要如何实现呢?
在java中简单的办法就是让消费者线程循环检查变量是否符合预期。如下面的伪代码,在while循环中设置不满足的条件,如果条件允许则退出while循环,从而完成消费的工作。示例代码如下:

while(value != desire){Thread.sleep(1000);
}
doSomeThing();

上面这段代码,条件不足时就睡眠一段时间,这样做的目的是防止过快的无效尝试,浪费CPU的资源。这种方式看似能解决问题,但是却存在以下问题:

  1. 难以确保及时性
  2. 难以降低开销

对于以上问题,Java中内置了等待/通知机制。等待/通知相关方法是任意Java对象都具备的,因为其定义在所有对象的超类java.lang.Object上。相关方法描述如下:

方法名称描述
notify()通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到该对象的锁。
notifyAll()通知所有等待在该对象上的线程
wait()调用该方法的线程进入WAITING状态,只有等待另外线程的通知或者中断才能返回。在调用wait方法后,会释放对象的锁。
wait(long)超时等待一段时间,参数是毫秒,也就是等待长达n毫秒后,会没有通知就可以超时返回。调用该方法的线程会进入TIMED_WAITING状态
wait(long,int)对于超时等待更细粒度的控制,可以达到纳秒

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程基于对象O来完成交互。对于wait,notify,notifyAll的使用示例代码如下:

public class WaitNotify {static boolean flag=true;static final Object lock=new Object();static class Wait implements Runnable{@Overridepublic void run() {synchronized (lock){while (flag){System.out.println(Thread.currentThread()+" flag is true.wait "+ LocalDateTime.now());try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread()+" flag is false. running "+LocalDateTime.now());}}}static class Notify implements  Runnable{@Overridepublic void run() {synchronized (lock){System.out.println(Thread.currentThread()+" hold lock. notify "+LocalDateTime.now());lock.notify();flag=false;try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (lock){System.out.println(Thread.currentThread()+" hold lock again. sleep"+LocalDateTime.now());try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) throws InterruptedException {Thread waitThread=new Thread(new Wait(),"WaitThread");waitThread.start();TimeUnit.SECONDS.sleep(1);Thread notify=new Thread(new Notify(),"NotifyThread");notify.start();}
}

执行结果如下:
image.png
从上述的例子中,有以下使用wait,notify,notifyall的细节:

  1. 调用wait、notify、notifyAll需要先对调用对象加锁
  2. 调用wait方法之后,线程状态由RUNNING变为WAITING,并将当前线程放到对象上的等待队列中。
  3. notify、notifyAll方法调用之后,等待线程依旧不会从wait返回,需要调用notify、notifyAll的线程释放锁之后,等待线程才有机会从wait返回。
  4. notify方法将等待队列中的一个线程从等待队列移动到同步队列中,notifyAll方法则是将等待队列中所有的线程全部移动到同步队列中。被移动的线程从WAITING变为BLOCKED。
  5. 从wait方法返回的前提是从对象获取到锁。

上述代码运行过程如下:
image.png
在图中,首先WaitThread获取到对象的锁,然后调用wait方法,从而放弃了锁,并进入等待队列中。由于WaitThread释放了锁,NotifyThead线程获取到对象的锁,并调用了线程的notify方法,将WaitThread从等待队列中转移到同步队列中,此时WaitThread线程的状态变为阻塞状态,在NotifyThread线程释放锁之后,WaitThread再次获取到锁,并从wait方法返回继续执行。

3.3、等待/通知经典范式

我们可以从上述示例代码WaitNotify中提炼出等待/通知的经典范式。改范式分为两部分,分别针对等待方(消费者)和通知方(生产者)。
等待方(消费者)遵循的原则如下:

  1. 获取对象的锁
  2. 如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件
  3. 条件满足继续执行对应的逻辑

对应的伪代码如下:

synchronize(对象){while(条件不满足){对象.wait();}对应的处理逻辑
}

通知方(生产者)遵循的原则如下:

  1. 获取对应的锁
  2. 改变条件
  3. 通知所有等待在对象上的线程

对应的伪代码如下:

synchronize(对象){改变条件;对象.notifyAll();
}

4、管道输入/输出流

管道输入/输出流和普通输入/输出流或者网络输入/输出流的区别在于,管道输入输出流主要用于线程间的数据传输,而传输的媒介就是内存。
管道输入输出流的实现主要分为PipedOutputStream、PipedInputStream、PipedReader、PipedWriter,前两种面向字节,后两种面向字符。示例代码如下:

public class Piped {static class Print implements Runnable{private PipedReader in;public Print(PipedReader in){this.in=in;}@Overridepublic void run() {int receive=0;while (true) {try {if ((receive=in.read())!=-1){System.out.print((char)receive);}} catch (IOException e) {e.printStackTrace();}}}}public static void main(String[] args) throws IOException {PipedWriter out=new PipedWriter();PipedReader in=new PipedReader();out.connect(in);Thread printThread=new Thread(new Print(in),"PrintThread");printThread.start();int receive=0;while (true) {try {if ((receive=System.in.read())!=-1) {out.write(receive);}} catch (IOException e) {e.printStackTrace();}}}
}

执行结果如下:
image.png
在示例中,创建PringThread线程用来接受main方法的输入,任何main方法线程的输入均通过PipedWriter写入,而PringThread线程的另一端通过PipedReader将内容读出并打印。
对于pip流必须先进行绑定,也就是调用connect()方法,如果没有将输入/输出流绑定起来,那么对于流的访问会抛出异常。

5、Thread.join()的使用

如果一个线程A执行了thread.join()语句,其含义就是线程A等待线程thread执行完之后从thread.join()返回,并继续执行自己的代码。线程Thread除了提供join()方法外,还有join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在指定的超时时间内没有终止,那么将会从thread.join方法中返回。示例代码如下:

public class Join {static class Domino implements Runnable{private Thread thread;public Domino(Thread thread){this.thread=thread;}@Overridepublic void run() {try {thread.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+" terminate.");}}public static void main(String[] args) throws InterruptedException {Thread pre=Thread.currentThread();for (int i=0;i<10;i++){Thread thread=new Thread(new Domino(pre),String.valueOf(i));thread.start();pre=thread;}TimeUnit.SECONDS.sleep(5);System.out.println(Thread.currentThread().getName() + " terminate.");}
}

执行结果如下:
image.png
从上述输出可以看到,每个线程的终止,都依赖于前一个线程的终止。

6、ThreadLocal的使用

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的值。
可以通过set(T)方法来设置一个值,在当前线程下在通过get()方法获取到原先设置的值。示例代码如下:

public class Profiler {private static final ThreadLocal<Long> TIME_THREAD_LOCAL=new ThreadLocal<Long>(){protected Long init(){return System.currentTimeMillis();}};public static void begin(){TIME_THREAD_LOCAL.set(System.currentTimeMillis());}public static long end(){return System.currentTimeMillis()-TIME_THREAD_LOCAL.get();}public static void main(String[] args) throws InterruptedException {Profiler.begin();TimeUnit.SECONDS.sleep(1);System.out.println("Cost: "+Profiler.end()+" mills");}
}

运行结果如下:
image.png

ThreadLocal结构

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

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

相关文章

SQL面试题练习 —— 计算次日留存率

题目 现有用户登录记录表&#xff0c;已经按照用户日期进行去重处理。以用户登录的最早日期作为新增日期&#xff0c;请计算次日留存率是多少。 样例数据 ----------------------- | user_id | login_date | ----------------------- | aaa | 2023-12-01 | | bbb …

ATmega328P加硬件看门狗MAX824L看门狗

void Reversewdt(){ //硬件喂狗&#xff0c;11PIN接MAX824L芯片WDIif (digitalRead(11) HIGH) {digitalWrite(11, LOW); //低电平} else {digitalWrite(11, HIGH); //高电平 }loop增加喂狗调用 void loop() { …… Reversewdt();//喂狗 }

【活动】开源与闭源大模型:探索未来趋势的双轨道路

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 开源与闭源大模型&#xff1a;探索未来趋势的双轨道路引言一、开源大模型&#…

设计模式使用(成本扣除)

前言 名词解释 基础名词 订单金额&#xff1a;用户下单时支付的金额&#xff0c;这个最好理解 产品分成&#xff1a;也就是跟其他人合做以后我方能分到的金额&#xff0c;举个例子&#xff0c;比如用户订单金额是 100 块&#xff0c;我方的分成是 80%&#xff0c;那么也就是…

【cocos creator 】生成六边形地图

想要生成一个六边形组成的地图 完整代码示例 以下是完整的代码示例&#xff0c;包含了注释来解释每一步&#xff1a; cc.Class({extends: cc.Component,properties: {hexPrefab: {default: null,type: cc.Prefab},mapWidth: 10, // 网格的宽度&#xff08;六边形的数量&am…

探索微软Edge开发者工具:优化前端开发的艺术与科学

探索微软Edge开发者工具&#xff1a;优化前端开发的艺术与科学 引言&#xff1a;Edge开发者工具概览一、基础操作&#xff1a;步入DevTools的大门1.1 启动与界面布局1.2 快速导航与定制 二、元素审查与样式调整2.1 精准元素选取2.2 实时CSS编辑2.3 自动完成与内联文档 三、Java…

【Linux网络】端口及UDP协议

文章目录 1.再看四层2.端口号2.1引入linux端口号和进程pid的区别端口号是如何生成的传输层有了pid还设置端口号端口号划分 2.2问题2.3netstat 3.UDP协议3.0每学一个协议 都要讨论一下问题3.1UDP协议3.2谈udp/tcp实际上是在讨论什么&#xff1f; 1.再看四层 2.端口号 端口号(Po…

基于51单片机的数字频率计(电路图+pcb+论文+仿真+源码)

于51单片机的数字频率计 设计的频率计范围能够达到1HZ-1MHZ(实际上51单片机达不到这个范围&#xff0c;不要在实验环境下进行)&#xff0c;这个是课设来着&#xff0c;用Proteus仿真实现的&#xff0c;给有需要的同学参考一下 仿真原理图如下&#xff08;proteus仿真工程文件可…

基于移动多媒体信源与信道编码调研

前言 移动多媒体是指在移动通信环境下&#xff0c;通过无线网络传输的音频、视频、图像等多种媒体信息。移动多媒体的特点是数据量大、传输速率高、服务质量要求高&#xff0c;因此对信源编码和信道编码的性能提出了更高的要求。 本文对进3年的移动多媒体信源与信道编码的研究…

1.存储部分

1.Flash Memory--闪速存储器&#xff08;注&#xff1a;U盘&#xff0c;SD卡就是闪存&#xff09;在EEPROM基础上发展而来的&#xff0c;断电后也能保存信息&#xff0c;且可进行多次 快速擦除重写。注意&#xff1a;由于闪存需要先擦除再写入&#xff0c;因此闪存写的速度要比…

详解最新版RabbitMQ 基于RPM 方式的安装

如何选择安装版本 已经不支持的发布系列 版本最后补丁版本首次发布时间停止更新时间3.73.7.282017年11月28日2020年09月30日3.63.6.162015年12月22日2018年05月31日3.53.5.82015年03月11日2016年10月31日3.43.4.42014年10月21日2015年10月31日3.33.3.52014年04月02日2015年03…

Python01:初入Python(Mac)

Python环境准备 下载Python&#xff1a;官网https://www.python.org/ 下载PyCharm&#xff1a;官网https://www.jetbrains.com/pycharm/download Python与PyCharm的关系 Python&#xff08;解释器&#xff09;&#xff1a;机器语言—>翻译人员–>翻译成电脑能读懂的 PyC…

Pytorch深度学习实践笔记3

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;pytorch深度学习 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 视频来自【b站刘二大人】 目录 1 梯度下降&#…

STM32 学习——1. STM32最小系统

这是一个最小系统的测试&#xff0c;LED灯会进行闪烁。选用PC13口&#xff0c;因为STM32F103C8T6 硬件开发板中&#xff0c;这个端口是一个LED 1. proteus8.15 原理图 2. cubemx 新建工程 3. keil 代码 while (1){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);HAL_Delay(100);…

linux文件权限常用知识点,基于Linux(openEuler、CentOS8)

目录 知识点常用实例 知识点 真实环境文件显示 解读 常用实例 文件所有者 chown -R nginx:nginx /home/source目录权限(R选填必须大写<遍历子文件夹及文件>) chmod -R 755 /home/sourcechmod -R 777 /home/source

为什么推荐前端用WebStorm软件编程?

一、介绍 WebStorm是由JetBrains公司开发的一款JavaScript开发工具&#xff0c;被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。它支持JavaScript、ECMAScript 6、TypeScript、CoffeeScript、Dart和Flow等多种语言的代码…

【vue】封装的天气展示卡片,在线获取天气信息

源码 <template><div class"sen_weather_wrapper"><div class"sen_top_box"><div class"sen_left_box"><div class"sen_top"><div class"sen_city">山东</div><qctc-time cl…

unreal engine 5.0.3 创建游戏项目

根据虚幻官网介绍&#xff0c;虚幻引擎5可免费用于创建线性内容、定制项目和内部项目。你可以免费用它开发游戏&#xff0c;只有当你的产品营收超过100万美元时&#xff0c;才收取5%的分成费用。所以目前国内也有许多游戏厂商在使用UE制作游戏。UE5源码也已开源&#xff0c;有U…

多线程(C++11)

多线程&#xff08;C&#xff09; 文章目录 多线程&#xff08;C&#xff09;前言一、std::thread类1.线程的创建1.1构造函数1.2代码演示 2.公共成员函数2.1 get_id()2.2 join()2.3 detach()2.4 joinable()2.5 operator 3.静态函数4.类的成员函数作为子线程的任务函数 二、call…

解释JAVA语言中关于方法的重载

在JAVA语言中&#xff0c;方法的重载指的是在同一个类中可以存在多个同名方法&#xff0c;但它们的参数列表不同。具体来说&#xff0c;重载的方法必须满足以下至少一条条件: 1. 参数个数不同。 2. 参数类型不同。 3. 参数顺序不同。 当调用一个重载方法时&#xff0c;编译器…