并发编程之深入理解Java线程

并发编程之深入理解Java线程

线程基础知识

线程和进程

进程

  • 程序由指令和数据组成、但这些指令要运行,数据要读写,就必须要将指令加载至CPU、数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的。
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程
  • 操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是系统资源分配的最小单位。

线程

  • 线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须由一个父进程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。
  • 线程,有时也被称为轻量级进程,是操作系统调度(CPU调度)执行的最小单位
进程和线程的区别
  • 进程基本上是相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算的进程通信称为IPC(Inter-processcommunication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如HTTP
  • 线程通信相对简单,因为他们共享进程内的内存,一个例子是多个进程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般要比进程上下文切换低
进程间通信的方式
  • 管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它允许无亲缘关系线程间进行通信
  • 信号(signal):信号是软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号于处理器收到一个中断请求效果上是一致的。
  • 消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量优先的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取消息。
  • 共享内存(shared memory):最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中的数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
  • 信号量(semaphore):主要作为进程之间及同一种进程之间得同步和互斥手段
  • 套接字(socket):这是一种更为一般得进程间得通信机制,它可用于网络中不同机器之间的进程间通信
线程的同步互斥
  • 线程同步是指线程之间具有一种制约关系,一个线程的执行依赖于另一个线程的消息,当它没有得到另一个线程的消息时应等待,知道消息到达时才会被唤醒。
  • 线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排他性。当有若干个线程都要使用某一共享资源是,任何时刻最多只允许一个线程去使用,其他要使用该资源的线程必须要等待,直到占用资源者释放该资源。线程互斥可以看成一种特殊的线程同步。
四种线程同步互斥的控制方法
  • 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问(在一段时间内只允许一个线程访问的资源就是临界资源)
  • 互斥量:为协调共同对一个共享资源的单独访问而设计的
  • 信号量:为控制一个具有有限数量用户资源而设计。
  • 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始
上下文切换(Context switch)

上下文切换是指CPU(中央处理单元)从一个线程或进程到另一个线程或进程的切换。

进程是程序的一个执行实例。在Linux中,线程是轻量级进程,可以并行运行,并与父进程(即创建线程的进程)共享一个地址空间和其他资源

上下文是CPU寄存器和程序计时器在任何时间点的内容

寄存器是CPU内部的一小部分非常快的内存(相对于CPU外部较慢的RAM主内存),它通过提供对常用值的快速访问来加快计算机程序的执行。

程序计数器是一种专门的寄存器,它指示CPU在其指令序列中的位置,并保存着正在执行的指令的地址或下一条要执行的指令的地址,这取决于具体的系统

image-20240214215412696

上下文切换可以更详细地描述为内核(即操作系统的核心)对CPU上的进程(包括线程)执行以下活动

  • 暂停一个进程的处理,并将该进程的CPU状态(即上下文)存储在内存中的某个地方
  • 从内存中获取下一个进程的上下文,并在CPU的寄存器中恢复它。
  • 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。

image-20240214215657229

上下文切换只能在内核模式下发生。内核模式是CPU的特权模式,其中只有内核运行,并提供对所有内存位置和所有其他系统资源的访问。其他程序(包括应用程序)最初在用户模式下运行,但他们可以通过系统调用运行部分内核代码。

内核模式(Kernal Mode) vs 用户模式(User Mode)

Kernal Mode

  • 在内核模式中,执行代码可以完全且不受现在地访问底层硬件。它可以执行任何CPU指令和引用任何内存地址。内核模式通常为操作系统最低级别、最受信任的功能保留。内核模式下的崩溃是灾难性的;他们会让整个电脑瘫痪

User Mode

  • 在用户模式下,执行代码不能直接访问硬件或引用内存。在用户模式下运行的代码必须委托给系统api来访问硬件或内存。由于这种隔离提供的保护,用户模式下的崩溃总是可恢复的。

image-20240214220302903

CPU保护模式

image-20240214220336301

应用程序以下几种情况会切换到内核模式:

  • 系统调用:当应用程序需要执行诸如读写文件、创建进程、建立网络连接等操作时,需要通过系统调用请求操作系统提供服务。
  • 异常事件:当程序执行出错,如除零错误、访问非法内存等,会触发异常,操作系统需要切换到内核态来处理这些异常。
  • 设备中断:当外部设备(如键盘、鼠标、网络接口卡等)发出中断信号时,操作系统需要切换到内核态来处理这些中断

上下文切换是多任务操作系统的一个基本特性。在多任务操作系统中,多个进程似乎同时在一个CPU上执行,彼此之间互不干扰。这种并发的错觉是通过快速连续发生的上下文切换(每秒数十次或数百次)来实现的。这些上下文切换发生的原因是进程自愿放弃他们在CPU中的事件,或者是调度器在进程耗尽其CPU时间片时进行切换的结果。

上下文切换通常是计算机密集型的。就CPU时间而言,上下文切换对系统来说是一个巨大的成本,时间上,它可能是操作系统上成本最高的操作。因此,操作系统设计中的一个主要焦点是尽可能地避免不必要的上下文切换。与其他操作系统(包括一些其他类unix系统)相比,Linux的众多优势之一就是它的上下文切换和模式切换成本极低。

通过命令查看CPU上下文切换情况

Linux可以通过命令统计CPU上下文切换数据

# 可以看到整个操作系统每1秒CPU上下文切换的统计
vmstat 1

image-20240214221800673

其中cs列就是CPU上下文切换的统计。CPU上下文切换不等价于线程切换,很多操作会造成CPU上下文切换。

  • 线程、进程切换
  • 系统调用
  • 中断
查看某一个线程\进程的上下文切换

使用pidstat命令

  • 常用的参数:

    -u 默认参数,显示各个进程的 CPU 统计信息

    -r 显示各个进程的内存使用情况

    -d 显示各个进程的 IO 使用

    -w 显示各个进程的上下文切换

    -p PID 指定 PID

# 显示进程5598每一秒的切换情况
pidstat -w -p 5598 1

image-20240214222213640

其中cswch表示主动切换,nvcswsh表示被动切换。

  • 从进程的状态信息中查看

    通过命令 cat /proc/5598/status 查看进程的状态信息

    voluntary_ctxt_switches: 40469351

    nonvoluntary_ctxt_switches: 2268

    这2项就是该进程从启动到当前总的上下文切换情况。

操作系统层面线程生命周期

操作系统层面的线程的生命周期基本上可以用下图“五台模型”来描述,这五态分别是:初始状态、可运行状态、运行状态、休眠状态和中止状态

image-20240214222636764

  • 初始状态:指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层面,真正的线程还没有创建。
  • 可运行状态:指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
  • 当有空闲CPU时,操作系统会将其分配给一个处于可运行状态的线程,被分配到CPU的线程的状态就转换成了运行状态
  • 运行状态的线程如果调用一个阻塞的API(例如以阻塞方式读文件)或者等待某个时间(例如条件变量),那么线程的状态就会切换到休眠状态,同时释放CPU使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。
  • 线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。
查看进程线程的方法

windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • tasklist 查看进程
  • taskkill 杀死进程

Linux

  • ps -ef 查看所有进程
  • ps -fT -p 查看某个进程(PID)的所有线程
  • kill 杀死进程
  • top 按大写 H 切换是否显示线程
  • top -H -p 查看某个进程(PID)的所有线程

Java

  • jps 命令查看所有 Java 进程
  • jstack 查看某个 Java 进程(PID)的所有线程状态
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

Java线程详解

Java线程的实现方式

方式1:使用Thread类或继承Thread类

// 创建线程对象
Thread t = new Thread() {public void run() {// 要执行的任务}
};
// 启动线程

方式2:实现 Runnable 接口配合Thread

Runnable runnable = new Runnable() {public void run(){// 要执行的任务}
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程

方式3:使用有返回值的Callable

class CallableTask implements Callable<Integer> {@Overridepublic Integer call() throws Exception {return new Random().nextInt();}
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());

方式4:使用lambda

new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

本质上Java中实现线程只有一种方式,都是通过new Thread()创建线程,调用Thread#start启动线程最终会调用Thread#run方法

Java线程实现的原理

Jva线程执行为什么不能直接调用run()方法,而是要调用start()方法

run()方法是普通方法调用,start()会创建线程进程调用

https://www.processon.com/view/link/5f02ed9e6376891e81fec8d5

Java线程属于内核级线程

JDK1.2——基于操作系统原生线程模型来实现。Sun JDK,它的Windows版本和Linux版本都使用一对一的线程模型实现,一条Java线程就映射到一条轻量级进程之中。

内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。

用户级线程(User Level Thread,ULT):操作系统内核不知道应用线程的存在。

image-20240214230110067

协程

协程,英文Coroutines, 是一种基于线程之上,但又比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行),具有对内核来说不可见的特性。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

image-20240214230154684

协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

  • 线程的切换由操作系统调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。

  • 线程的默认stack大小是1M,而协程更轻量,接近1k。因此可以在相同的内存中开启更多的协程。

  • 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

注意: 协程适用于被阻塞的,且需要大量并发的场景(网络io)。不适合大量计算的场景。

Java线程的调度机制

线程调度是指系统为线程分配处理器使用权的过程,主要的调度方式分为两种,分别是协同式线程调度和抢占式线程调度。

协同式线程调度

  • 线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里。

抢占式线程调度

  • 每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞。

Java线程调度就是抢占式调度

希望系统能给某些线程多分配一些时间,给一些线程少分配一些时间,可以通过设置线程优先级来完成。Java语言一共10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两线程同时处于ready状态时,优先级越高的线程越容易被系统选择执行。但优先级并不是很靠谱,因为Java线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统。

public class SellTicketDemo implements Runnable {/*** 车票*/private int ticket;public SellTicketDemo() {this.ticket = 1000;}@Overridepublic void run() {while (ticket > 0) {synchronized (this) {if (ticket > 0) {try {// 线程进入暂时的休眠Thread.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}// 获取到当前正在执行的程序的名称,打印余票System.out.println(Thread.currentThread().getName()+ ":正在执行操作,余票:" + ticket--);}}Thread.yield();}}public static void main(String[] args) {SellTicketDemo demo = new SellTicketDemo();Thread thread1 = new Thread(demo, "thread1");Thread thread2 = new Thread(demo, "thread2");Thread thread3 = new Thread(demo, "thread3");Thread thread4 = new Thread(demo, "thread4");//priority优先级默认是5,最低1,最高10thread1.setPriority(Thread.MAX_PRIORITY);thread2.setPriority(Thread.MAX_PRIORITY);thread3.setPriority(Thread.MIN_PRIORITY);thread4.setPriority(Thread.MIN_PRIORITY);thread1.start();thread2.start();thread3.start();thread4.start();}
}

上述程序并不一定是thread1和thread2抢到的票最多

Thread.yield()的使用

用于暂停当前正在执行的线程,让出CPU的使用权,以允许其他线程执行,但是Thread.yield()并不能保证当前线程立即停止执行,具体的行为取决于线程调度器

Java线程的生命周期

Java语言中线程共有六种状态,分别是:

  • NEW(初始化状态)
  • RUNNABLE(可运行状态+运行状态)
  • BLOCKED(阻塞状态)
  • WAITING(无时限等待)
  • TIMED_WAITING(有时限等待)
  • TERMINATED(终止状态)

在操作系统层面,Java线程中的BLOCKED、WAITING、TIMED_WAITING 是一种状态,即休眠状态,也就是只要Java线程处于这三个状态,那么这个线程永远没有CPU的使用权

image-20240220204626915

从JavaThread的角度,JVM定义了一些针对Java Thread对象的状态(jvm.h)

image-20240220204709578

从OSThread的角度,JVM还定义了一些线程状态给外部使用,比如用jstack输出的线程堆栈信息中线程的状态(osThread.hpp)

image-20240220204805548

Thread常用的方法

sleep方法

  • 调用 sleep 会让当前线程从 Running 进入TIMED_WAITING状态,不会释放对象锁
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException,并且会清除中断标志
  • 睡眠结束后的线程未必会立刻得到执行
  • sleep当传入的参数为0时,和yield相同

yield方法

  • yield会释放CPU资源,让当前线程从Running进入Runnable状态,让优先级更高(至少是相同)的线程获得执行机会,不会释放对象锁
  • 假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比它优先级更高的线程
  • 具体的实现依赖于系统的任务调度器

join方法

  • 等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景
public class ThreadJoinDemo {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("t begin");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t finished");}});long start = System.currentTimeMillis();t.start();t.join();System.out.println("执行时间:" + (System.currentTimeMillis() - start));System.out.println("Main finish");}
}

stop方法

stop()方法已经被JDK废弃,原因就是stop()方法太过于暴力,强行把执行到一半的线程终止。

stop会释放锁,可能造成数据不一致

Java线程的中断机制
  • Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协同机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。被中断的线程有用完全的自主权,它即可以选择理解停止,也可以选择一段时间后停止,也可以选择压根不停止。

API的使用

  • interrupt():将线程的中断标志设置为true,不会停止线程
  • isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位
  • Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为false。
public class ThreadInterruptTest {static int i = 0;public static void main(String[] args) {System.out.println("begin");Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {while (true) {i++;System.out.println(i);// Thread.interrupted() 清除中断标志位   --> 只会输出一个 ==========// Thread.currentThread().isInterrupted() 不会清除中断标志位   -> 会输出十个==========if (Thread.interrupted()) {System.out.println("==========");}if (i == 10) {break;}}}});t1.start();t1.interrupt();}
}

利用中断机制优雅的停止线程

while (!Thread.currentThread.isInterrupted() && more work to do) {do more work
}
public class StopThread implements Runnable {@Overridepublic void run() {int count = 0;while (!Thread.currentThread().isInterrupted() && count < 1000) {System.out.println("count = " + count++);}System.out.println("线程停止 : stop thread");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new StopThread());thread.start();Thread.sleep(5);thread.interrupt();}
}

使用中断机制时要注意是否中断标志位被清除的情况

public class StopThread implements Runnable {@Overridepublic void run() {int count = 0;while (!Thread.currentThread().isInterrupted() && count < 1000) {System.out.println("count = " + count++);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();// 重新设置线程中断状态位trueThread.currentThread().interrupt();}}System.out.println("线程停止 : stop thread");}public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new StopThread());thread.start();Thread.sleep(5);thread.interrupt();}
}

处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个InterruptedException异常,同时清除中断信号,将中断标记位设置为false。这样就会导致while条件Thread.currentThread().isInterrupted()为false,程序会在不满足count < 1000这个条件时退出。如果不在catch中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有可能导致线程无法正确停止。

sleep可以被中断 抛出中断异常 : sleep interrupted,清除中断标志位

wait可以被中断 抛出中断异常 InterruptedException 清除中断标志位

Java线程间通信
  • volatile

volatile有两大特性,一个是可见性,二是有序性,禁止指令重排序,其中可见性就是让线程之间通信

public class VolatileDemo {private static volatile boolean flag = true;public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {while (true){if (flag){System.out.println("trun on");flag = false;}}}}).start();new Thread(new Runnable() {@Overridepublic void run() {while (true){if (!flag){System.out.println("trun off");flag = true;}}}}).start();}
}### 结果trun on
trun off
trun on
trun off
trun on
trun off
trun on
trun off
trun on
trun off
trun on
trun off
trun on
trun off
trun on
trun off
trun on
trun off
trun on
trun off
  • 等待唤醒(等待通知)机制

等待唤醒机制可以基于wait和notify方法来实现,在一个线程内调用改线程锁对象的wait方法,线程将进入等待队列进行等待直到被唤醒

public class WaitDemo {private static Object lock = new Object();private static boolean flag = true;public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {while (flag) {try {System.out.println("wait start .....");lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("wait end..........");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {if (flag) {synchronized (lock) {if (flag) {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}lock.notifyAll();System.out.println("notify ......");flag = false;}}}}}).start();}
}

LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待”许可“,调用unpark则为指定线程提供”许可“。使用它可以在任何场所使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但是注意连续多次的唤醒效果和一次唤醒是一样的。

public class LockSupportTest {public static void main(String[] args) {Thread parkThread = new Thread(new ParkThread());parkThread.start();System.out.println("唤醒parkThread");// 为指定线程parkThread提供许可LockSupport.unpark(parkThread);}static class ParkThread implements Runnable {@Overridepublic void run() {System.out.println("ParkThread开始执行");// 等待许可LockSupport.park();System.out.println("ParkThread执行完成");}}
}
  • 管道输入输出流

管道输入/输出流和普通文件输入/输出流或网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输媒介为内存。管道输入/输出主要包括了以下四种实现:

PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符

public class PipedTest {public static void main(String[] args) throws Exception {PipedWriter out = new PipedWriter();PipedReader in = new PipedReader();// 将输入流和输出流进行连接,否则在使用时会抛出IOExceptionout.connect(in);Thread printThread = new Thread(new Print(in), "PrintThread");printThread.start();int receive = 0;try {while ((receive = System.in.read()) != -1) {out.write(receive);}} finally {out.close();}}static class Print implements Runnable {private PipedReader in;public Print(PipedReader in) {this.in = in;}@Overridepublic void run() {int receive = 0;try {while ((receive = in.read()) != -1) {System.out.println((char) receive);}} catch (IOException e) {throw new RuntimeException(e);}}}
}
  • Thread.join

join可以理解成线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但是如果调用线程的join方法其实是已经失去了并行的意义,虽然存在多个线程,但是本质上还是串行的,最后join的实现是基于等待通知机制的。

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

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

相关文章

Jmeter内置变量 vars 和props的使用详解

JMeter是一个功能强大的负载测试工具&#xff0c;它提供了许多有用的内置变量来支持测试过程。其中最常用的变量是 vars 和 props。 vars 变量 vars 变量是线程本地变量&#xff0c;它们只能在同一线程组内的所有线程中使用&#xff08;线程组内不同线程之间变量不共享&#…

模型转换案例学习:等效替换不支持算子

文章介绍 Qualcomm Neural Processing SDK &#xff08;以下简称SNPE&#xff09;支持Caffe、ONNX、PyTorch和TensorFlow等不同ML框架的算子。对于某些特定的不支持的算子&#xff0c;我们介绍一种算子等效替换的方法来完成模型转换。本案例来源于https://github.com/quic/qidk…

并发编程(2)基础篇-管程

4 共享模型之管程 本章内容 共享问题synchronized线程安全分析Monitorwait/notify线程状态转换活跃性Lock 4.1 共享带来的问题 4.1.1 小故事 老王&#xff08;操作系统&#xff09;有一个功能强大的算盘&#xff08;CPU&#xff09;&#xff0c;现在想把它租出去&#xff…

2024 全国水科技大会暨第二届智慧水环境管理与技术创新论坛

论坛二&#xff1a;第二届智慧水环境管理与技术创新论坛 召集人&#xff1a;刘炳义 武汉大学智慧水业研究所所长、教授 为贯彻落实中共中央国务院印发《数字中国建设整体布局规划》和国务院关于印发《“十四五”数字经济发展规划》的通知&#xff0c;推动生态环境智慧治理&…

L2 清点代码库----PTA(疑问)

上图转自新浪微博&#xff1a;“阿里代码库有几亿行代码&#xff0c;但其中有很多功能重复的代码&#xff0c;比如单单快排就被重写了几百遍。请设计一个程序&#xff0c;能够将代码库中所有功能重复的代码找出。各位大佬有啥想法&#xff0c;我当时就懵了&#xff0c;然后就挂…

docker pullpush 生成镜像文件并push 到阿里云

pull docker docker pull ultralytics/ultralytics # 拉取yolov8的镜像仓库 docker run -it ultralytics/ultralytics # 运行镜像 conda create -n gsafety python3.8 # 创建环境 source activate gsafety # 激活环境 pip install -i https://pypi.tuna.tsinghua.edu.cn/simp…

糖尿病性视网膜病变(DR)的自动化检测和分期

糖尿病性视网膜病变&#xff08;DR&#xff09;的自动化检测和分期 提出背景DR的阶段及其特征 历年解法计算机视觉方法多分类方法 新的解法深度学习方法迁移学习大模型多模型集成全流程分析 总结特征1&#xff1a;图像分割特征2&#xff1a;疾病分级特征3&#xff1a;治疗建议生…

开源模型应用落地-工具使用篇-获取文本向量(五)

一、前言 在之前学习的"开源模型应用落地-工具使用篇"系列文章中&#xff0c;我们已经学会了如何使用向量数据库。然而&#xff0c;还有一个问题一直未解决&#xff0c;那就是如何处理文本向量。在本文中&#xff0c;我们将继续深入学习关于向量的知识&#xff0c;特…

常见消息中间件

ActiveMQ 我们先看ActiveMQ。其实一般早些的项目需要引入消息中间件&#xff0c;都是使用的这个MQ&#xff0c;但是现在用的确实不多了&#xff0c;说白了就是有些过时了。我们去它的官网看一看&#xff0c;你会发现官网已经不活跃了&#xff0c;好久才会更新一次。 它的单机吞…

2024年学习的最高薪酬编程语言

2024年学习的最高薪酬编程语言 10. Scala Scala是一种在Java虚拟机&#xff08;JVM&#xff09;上运行的函数式编程语言。它通常用于大数据处理、机器学习和后端Web开发。 关于Scala编程语言及其常见用途的要点如下&#xff1a; Scala是一种通用编程语言&#xff0c;运行在J…

mac真的安装不了vmware吗 mac如何安装crossover crossover序列号从哪里买 购买正版渠道

有些用户可能想在mac上运行一些只能在windows上运行的软件&#xff0c;比如游戏、专业软件等。这时候&#xff0c;就需要用到虚拟机技术&#xff0c;也就是在mac上安装一个可以模拟其他操作系统的软件&#xff0c;比如vmware或者crossover。那么&#xff0c;mac真的安装不了vmw…

【前端素材】推荐优质后台管理系统Xoric平台模板(附源码)

一、需求分析 当我们从多个层次来详细分析后台管理系统时&#xff0c;可以将其功能和定义进一步细分&#xff0c;以便更好地理解其在不同方面的作用和实际运作。 1. 功能层次 a. 用户管理功能&#xff1a; 用户注册和登录&#xff1a;管理用户账户的注册和登录过程。权限管…

K8S故障处理指南:网络问题排查思路

1. 前言 对于私有化环境&#xff0c;客户的网络架构&#xff0c;使用的云平台存在着各种差异&#xff0c;K8S网络可能会出现各种问题&#xff0c;此文着重讲解遇到此种问题的排查方法和思路&#xff0c;不会涉及相关网络底层技术描述. 环境说明 由于我们的k8s网络组件默认使…

5.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-测试需求与需求拆解

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;模拟游戏登陆器启动游戏并且完成注入 首先正常分析软件程序有没有漏洞&#xff0c;需要通过它的操作侵入&#xff0c;比如买东西&#xff0c;就通过买东西的按钮它背后有源代码就看源代码&#xff0c…

【PythonGIS】基于Python融合矢量数据(多面合一)

之前发过使用批量合并矢量数据的文章&#xff1a;【Python&GIS】基于Python批量合并矢量数据&#xff0c;正好前段时间有需求把矢量数据进行融合&#xff0c;然后就编了一段融合矢量数据的代码。今天就和大家分享一下如何使用Python对矢量数据实现融合的操作。 1.定义 首先…

基于Embedding召回和DSSM双塔模型

文章目录 基于Embedding召回介绍基于Embedding召回算法分类I2I召回U2I召回 DSSM模型DSSM双塔模型层次 基于Embedding召回介绍 基于embedding的召回是从内容文本信息和用户查询的角度出发&#xff0c;利用预训练的词向量模型或深度学习模型&#xff0c;将文本信息转换成向量进行…

三、创建脚手架和脚手架分析

三、创建脚手架 一、环境准备 1、安装node.js **下载地址&#xff1a;**https://nodejs.org/zh-cn/界面展示 2、检查node.js版本 查看版本的两种方式 node -vnode -version 出现版本号则说明安装成功&#xff08;最新的以官网为准&#xff09; 3、为了提高我们的效率&…

深度学习从入门到不想放弃-7

上一章的内容 深度学习从入门到不想放弃-6 (qq.com) 今天讲的也算基础(这个系列后来我一寻思,全是基础 ),但是可能要着重说下,今天讲前向计算和反向传播,在哪儿它都永远是核心,不管面对什么模型 前向计算: 有的叫也叫正向传播,正向计算的,有的直接把前向的方法梯度下…

祖龙娱乐 x Incredibuild

关于祖龙娱乐 祖龙娱乐有限公司&#xff08;下文简称“祖龙娱乐”&#xff09;是一家总部位于北京的移动游戏开发公司&#xff0c;成立于 2014 年&#xff0c;拥有成功的大型多人在线角色扮演游戏移动游戏组合&#xff0c;如《六龙争霸》、《梦幻诛仙》和《万王之王 3D》。公司…

微信小程序 ---- 慕尚花坊 项目初始化

目录 项目介绍 01. 项目概述 02. 项目演示 03. 项目技术栈 04. 接口文档 申请开发权限 项目初始化 01. 创建项目与项目初始化 02. 自定义构建 npm 集成Sass 03. 集成项目页面文件 04. VsCode 开发小程序项目 项目介绍 01. 项目概述 [慕尚花坊] 是一款 同城鲜花订购…