并发编程笔记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.mzph.cn/diannao/15551.shtml

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

相关文章

CS 下载安装详解

目录 CS简介&#xff1a; CS下载地址&#xff1a; CS的安装&#xff1a; CS简介&#xff1a; CS为目前渗透中常用的一款工具&#xff0c;它的强大在于控制windows木马&#xff0c;CS主要控制windows木马。 CS下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/…

WordPress Country State City Dropdown CF7插件 SQL注入漏洞复现(CVE-2024-3495)

0x01 产品简介 Country State City Dropdown CF7插件是一个功能强大、易于使用的WordPress插件,它为用户在联系表单中提供国家、州/省和城市的三级下拉菜单功能,帮助用户更准确地填写地区信息。同时,插件的团队和支持也非常出色,为用户提供高质量的服务。 0x02 漏洞概述 …

【Pytorch】【MacOS】14.m1芯片使用mps进行深度模型训练

读者要先自行安装python以及anaconda&#xff0c;并且配置pytorch环境 第一步 测试环境 import torch # 判断macOS的版本是否支持 print(torch.backends.mps.is_available()) # 判断mps是否可用 print(torch.backends.mps.is_built())如果第一个语句为False&#xff0c;说明当前…

Python简介

Python简介 1. Python定义 Python 是一种简单易学并且结合了解释性、编译性、互动性和面向对象的脚本语言。Python提供了高级数据结构&#xff0c;它的语法和动态类型以及解释性使它成为广大开发者的首选编程语言。 Python 是解释型语言&#xff1a; 开发过程中没有了编译这个环…

AIGC-常见图像质量评估MSE、PSNR、SSIM、LPIPS、FID、CSFD,余弦相似度----理论+代码

持续更新和补充中…多多交流&#xff01; 参考: 图像评价指标PNSR和SSIM 函数 structural_similarity 图片相似度计算方法总结 MSE和PSNR MSE: M S E 1 m n ∑ i 0 m − 1 ∑ j 0 n − 1 [ I ( i , j ) − K ( i , j ) ] 2 MSE\frac{1}{mn}\sum_{i0}^{m-1}\sum_{j0}^{n-1}[…

汽车展厅应用客流统计,洞察客户规律,完成热门车型分析

在汽车展厅中&#xff0c;客流统计正逐渐成为一项不可或缺的重要工具&#xff0c;它帮助我们洞察客户规律&#xff0c;从而能够更好地完成热门车型分析。 一、客流统计-客户画像分析 客流统计下的客户画像构建为我们提供了深入了解客户的途径。通过对进入展厅的人群进行细致分析…

2007NOIP普及组真题 4. Hanoi双塔问题

线上OJ&#xff1a; 【07NOIP普及组】Hanoi双塔问题 题解分析 1、本题考的其实不是Hanoi塔&#xff0c;而是瞪眼法&#xff08;数学推导&#xff09;和高精度。 2、本题不需要输出移动的顺序&#xff0c;只是输出移动的次数即可。 核心思想&#xff1a; 1、从上述图中&#x…

常见算法(3)

1.Arrays 它是一个工具类&#xff0c;主要掌握的其中一个方法是srot&#xff08;数组&#xff0c;排序规则&#xff09;。 o1-o2是升序排列&#xff0c;o2-o1是降序排列。 package test02; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparat…

PostgreSQL用户与角色简述

简述 PostgreSQL通过角色&#xff08;role&#xff09;来控制数据库的访问权限。角色可以拥有数据库对象&#xff08;比如表、函数等&#xff09;&#xff0c;并允许将这些对象的权限授予其他角色&#xff0c;从而实现对象访问的控制。角色&#xff08;role&#xff09;包含了…

虹科Pico汽车示波器 | 免拆诊断案例 | 2012 款雪佛兰科鲁兹车偶尔多个故障灯异常点亮

故障现象 一辆2012款雪佛兰科鲁兹车&#xff0c;搭载1.8 L 发动机&#xff0c;累计行驶里程约为9.6万km。该车组合仪表上的发动机故障灯、ABS故障灯及动力转向故障灯偶尔异常点亮&#xff0c;同时发动机转速表和发动机冷却液温度表的指针会突然归零&#xff0c;严重时发动机无…

独享IP是原生IP吗?二者有何区别?

原生IP&#xff1a; 原生IP是指由Internet服务提供商&#xff08;ISP&#xff09;直接分配给用户的IP地址&#xff0c;这些IP地址通常反映了用户的实际地理位置和网络连接。原生IP是用户在其所在地区或国家使用的真实IP地址&#xff0c;与用户的物理位置直接相关。在跨境电商中…

牛客NC367 第K个n的排列【困难 dfs,全排列问题 Java/Go/PHP/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/1595969179464e4c940a90b36abb3c54 思路 全排列问题本文提供的答案在力扣同一道题60. 排列序列&#xff0c;超时了但是截止文章发表日&#xff0c;牛客上是能通过全部测试用例的Java代码 import java.util.*;pu…

Redis - 优惠卷秒杀

场景分析 为了避免对数据库造成压力&#xff0c;我们在新增优惠卷的时候&#xff0c;可以将优惠卷的信息储存在Redis中&#xff0c;这样用户抢购的时候访问优惠卷信息&#xff0c;通过Redis读取信息。 抢购流程&#xff1a; 业务分析 既然在新增优惠卷的时候&#xff0c;我…

【c语言】了解指针,爱上指针(5)

了解指针&#xff0c;爱上指针&#xff08;5&#xff09; 回调函数qsort函数冒泡排序模拟实现qsort函数 回调函数 回调函数&#xff1a;就是一个通过函数指针调用的函数。 把函数的指针作为参数传给另一个函数&#xff0c;当这个指针被用来调用指向的函数时&#xff0c;此时被…

驱动编译报error: negative width in bit-field ‘<anonymous>’错误

错误如下图所示&#xff1a; 代码如下&#xff1a; 问题点&#xff1a;module_param的其他用户的权限参数上。 在Linux中&#xff0c;文件权限由读(r)、写(w)、执行(x)权限组成&#xff0c;分别对应数值4、2、1。 第一位0是占位符&#xff0c;在这里没有意义&#xff0c;因为…

如何使用DotNet-MetaData识别.NET恶意软件源码文件元数据

关于DotNet-MetaData DotNet-MetaData是一款针对.NET恶意软件的安全分析工具&#xff0c;该工具专为蓝队研究人员设计&#xff0c;可以帮助广大研究人员轻松识别.NET恶意软件二进制源代码文件中的元数据。 工具架构 当前版本的DotNet-MetaData主要由以下两个部分组成&#xf…

java图书电子商务网站的设计与实现源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的图书电子商务网站的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 图书电子商…

基于附带Attention机制的seq2seq模型架构实现英译法的案例

模型架构 先上图 我们这里选用GRU来实现该任务&#xff0c;因此上图的十个方框框都是GRU块&#xff0c;如第二张图&#xff0c;放第一张图主要是强调编码器的输出是作用在解码器每一次输入的观点&#xff0c;具体的详细流程图将在代码实现部分给出。 编码阶段 1. 准备工作…

【C++进阶】AVL树

0.前言 前面我们已经学习过二叉搜索树了&#xff0c;但如果我们是用二叉搜索树来封装map和set等关联式容器是有缺陷的&#xff0c;很可能会退化为单分支的情况&#xff0c;那样效率就极低了&#xff0c;那么有没有方法来弥补二叉搜索树的缺陷呢&#xff1f; 那么AVL树就出现了&…

6.小程序页面布局 - 账单明细

文章目录 1. 6.小程序页面布局 - 账单明细1.1. 竞品1.2. 布局分析1.3. 布局demo1.4. 页面实现-头部1.5. 账单明细1.5.1. 账单明细-竞品分析1.5.2. 账单明细-实现1.5.2.1. 账单明细-实现-mock数据1.5.2.2. 每日收支数据的聚合整理1.5.2.3. 页面scroll-view 1.6. TODO 1. 6.小程序…