并發編程之深入理解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.pswp.cn/news/696402.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696402.shtml
英文地址,請注明出處:http://en.pswp.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…

基礎小白快速入門Python->Python中的類

什么是類&#xff1f; 在編程語言中&#xff0c;類&#xff08;Class&#xff09;是一個用于創建對象的藍圖或模板。它定義了對象的屬性&#xff08;也稱為成員變量&#xff09;和方法&#xff08;也稱為成員函數&#xff09;。類是面向對象編程&#xff08;OOP&#xff09;的…

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;特…

Redis的哨兵系統

Redis 哨兵&#xff08;Sentinel&#xff09;系統是一種用于管理多個 Redis 服務器的系統&#xff0c;其主要目標是提供監控、通知、自動故障轉移和服務發現功能。哨兵系統能夠在 Redis 實例出現問題時自動進行故障轉移&#xff0c;確保系統的高可用性。其工作原理如下&#xf…

常見消息中間件

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…

2024年華為OD機試真題-貪心歌手-Python-OD統一考試(C卷)

題目描述: 一個歌手準備從A城去B城參加演出。 1) 按照合同,他必須在T天內趕到。 3) 歌手不能往回走。 4) 每兩座城市之間需要的天數都可以提前獲知。 5) 歌手在每座城市都可以在路邊賣唱賺錢。經過調…

【前端素材】推薦優質后臺管理系統Xoric平臺模板(附源碼)

一、需求分析 當我們從多個層次來詳細分析后臺管理系統時&#xff0c;可以將其功能和定義進一步細分&#xff0c;以便更好地理解其在不同方面的作用和實際運作。 1. 功能層次 a. 用戶管理功能&#xff1a; 用戶注冊和登錄&#xff1a;管理用戶賬戶的注冊和登錄過程。權限管…

K8S故障處理指南:網絡問題排查思路

1. 前言 對于私有化環境&#xff0c;客戶的網絡架構&#xff0c;使用的云平臺存在著各種差異&#xff0c;K8S網絡可能會出現各種問題&#xff0c;此文著重講解遇到此種問題的排查方法和思路&#xff0c;不會涉及相關網絡底層技術描述. 環境說明 由于我們的k8s網絡組件默認使…

5.網絡游戲逆向分析與漏洞攻防-游戲網絡架構逆向分析-測試需求與需求拆解

內容參考于&#xff1a;易道云信息技術研究院VIP課 上一個內容&#xff1a;模擬游戲登陸器啟動游戲并且完成注入 首先正常分析軟件程序有沒有漏洞&#xff0c;需要通過它的操作侵入&#xff0c;比如買東西&#xff0c;就通過買東西的按鈕它背后有源代碼就看源代碼&#xff0c…

TypeScript學習筆記-基礎

一、type 和 interface type和 interface的區別&#xff1a;TypeScript 中文網: 文檔 - 日常類型 type類型別名和interface接口非常相似&#xff0c;在很多情況下可以在它們之間自由選擇。interface 的幾乎所有功能都在 type 中可用&#xff0c;主要區別在于無法重新打開類型…

【PythonGIS】基于Python融合矢量數據(多面合一)

之前發過使用批量合并矢量數據的文章&#xff1a;【Python&GIS】基于Python批量合并矢量數據&#xff0c;正好前段時間有需求把矢量數據進行融合&#xff0c;然后就編了一段融合矢量數據的代碼。今天就和大家分享一下如何使用Python對矢量數據實現融合的操作。 1.定義 首先…

基于Embedding召回和DSSM雙塔模型

文章目錄 基于Embedding召回介紹基于Embedding召回算法分類I2I召回U2I召回 DSSM模型DSSM雙塔模型層次 基于Embedding召回介紹 基于embedding的召回是從內容文本信息和用戶查詢的角度出發&#xff0c;利用預訓練的詞向量模型或深度學習模型&#xff0c;將文本信息轉換成向量進行…