Java內存模型(JMM)深度解析

1. 引言

在當今多核處理器和并發編程盛行的時代,Java工程師們在構建高性能、高可用系統時,常常會面臨復雜的線程安全挑戰。數據不一致、競態條件、死鎖等問題,不僅難以調試,更可能導致系統行為異常。這些問題的根源,往往深植于Java內存模型(Java Memory Model,簡稱JMM)的底層機制之中。JMM是Java語言規范中至關重要的一部分,它精確定義了Java程序中線程如何與內存進行交互,以及對共享變量的修改何時對其他線程可見,從而為并發編程提供了堅實的理論基礎和行為保障。

盡管JMM的重要性不言而喻,但許多Java開發者對其理解可能仍停留在抽象層面,甚至在實際開發中容易忽視其潛在影響。然而,當程序中出現難以復現的并發缺陷時,深入剖析JMM往往是撥開迷霧、定位問題的關鍵。JMM不僅是理解synchronizedvolatile等核心并發關鍵字工作原理的鑰匙,更是編寫健壯、高效并發程序的必備知識。

2. JMM基礎概念

2.1 硬件內存架構與Java內存模型

為了更好地理解JMM,我們首先需要了解現代計算機的硬件內存架構。在多處理器系統中,每個處理器都有自己的高速緩存(Cache),而所有處理器共享一個主內存(Main Memory)。CPU在執行計算時,通常會先將數據從主內存加載到自己的高速緩存中,計算完成后再將結果寫回主內存。這種緩存機制極大地提高了CPU的訪問速度,但也帶來了緩存一致性問題。當多個CPU同時操作同一個內存地址時,各自的緩存中可能存儲著不同的數據副本,導致數據不一致。

Java內存模型(JMM)正是為了解決這種硬件層面的緩存一致性問題而設計的。JMM屏蔽了底層硬件的復雜性,為Java程序員提供了一個統一的、抽象的內存視圖。它定義了程序中各個變量(包括實例字段、靜態字段和構成數組對象的元素)的訪問方式。JMM規定了所有變量都存儲在主內存中,而每條線程都有自己的工作內存(Working Memory),工作內存中保存了該線程使用到的變量的主內存副本。線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成。

2.2 主內存與工作內存

JMM中的主內存(Main Memory)對應于硬件內存中的主內存,它存儲了Java程序中所有的共享變量。共享變量是指在多個線程之間共享的變量,例如類的靜態變量、實例變量以及數組元素。工作內存(Working Memory)是每個線程私有的內存區域,它存儲了該線程所使用的共享變量的副本。當線程需要操作某個共享變量時,它會先從主內存中讀取該變量的副本到自己的工作內存中,然后對該副本進行操作。操作完成后,線程會將修改后的副本寫回主內存。

需要注意的是,JMM中的主內存和工作內存與Java虛擬機運行時數據區域中的堆、棧、方法區等并不是完全等價的概念。它們是JMM為了描述并發訪問共享變量時,對數據可見性、有序性和原子性的抽象概念。工作內存可以理解為處理器的高速緩存或者寄存器,而主內存則是共享的RAM。JMM通過對主內存和工作內存之間交互的規定,來保證多線程環境下數據的一致性。

3. JMM的核心特性

JMM主要圍繞并發編程中三個核心問題展開:可見性、有序性和原子性。理解這三個特性是掌握JMM的關鍵。

3.1 可見性(Visibility)

可見性是指當一個線程修改了共享變量的值時,其他線程能夠立即得知這個修改。在多核處理器架構下,由于每個CPU都有自己的高速緩存,一個線程對共享變量的修改可能僅僅是修改了自己工作內存中的副本,而沒有及時同步到主內存中。這會導致其他線程從主內存中讀取到的仍然是舊的值,從而引發數據不一致的問題。

問題示例:

假設有一個flag變量,初始值為false。線程A將其設置為true,而線程B在一個循環中不斷檢查flag的值。如果flag沒有被正確地同步到主內存,線程B可能永遠看不到flag的改變,從而導致死循環。

volatile關鍵字:保證可見性

Java提供了volatile關鍵字來保證共享變量的可見性。當一個變量被volatile修飾時,它具備以下兩個特性:

  1. 保證可見性:對volatile變量的寫操作會立即刷新到主內存,并且對volatile變量的讀操作會從主內存中重新加載最新值。這意味著,當一個線程修改了volatile變量的值,新值對于其他線程來說是立即可見的。
  2. 禁止指令重排序volatile還會禁止特定類型的指令重排序,這將在下一節“有序性”中詳細討論。

使用示例:

public class VisibilityExample {private static volatile boolean stop = false;public static void main(String[] args) throws InterruptedException {Thread worker = new Thread(() -> {while (!stop) {// do some work}});worker.start();Thread.sleep(1000);stop = true;System.out.println("Main thread set stop to true.");}
}

在這個例子中,stop變量被volatile修飾,確保了當主線程將stop設置為true時,worker線程能夠立即看到這個改變,從而終止循環。

3.2 有序性(Ordering)

有序性是指程序執行的順序。在Java內存模型中,為了提高性能,編譯器和處理器可能會對指令進行重排序。指令重排序是指在不改變單線程程序執行結果的前提下,調整指令的執行順序。雖然這種重排序在單線程環境下是安全的,但在多線程環境下可能會導致意想不到的問題。

問題示例:

class ReorderingExample {int x = 0;boolean flag = false;public void writer() {x = 42;        // (1)flag = true;   // (2)}public void reader() {if (flag) {    // (3)System.out.println(x); // (4)}}
}

writer方法中,指令(1)和指令(2)可能會被重排序。如果flag = true先于x = 42執行,并且在flag被設置為true后,reader線程立即執行,那么reader線程可能會看到flagtrue,但x的值仍然是0,而不是42,從而導致錯誤的結果。

happens-before原則:JMM的基石

JMM通過happens-before原則來保證多線程操作的有序性。happens-before原則是JMM中一個非常重要的概念,它定義了操作之間的偏序關系,即如果一個操作A happens-before另一個操作B,那么操作A的結果對操作B是可見的,并且操作A的執行順序先于操作Bhappens-before原則是判斷數據是否存在競爭、程序是否安全的主要依據。即使在指令重排序的情況下,只要符合happens-before原則,程序的執行結果就是正確的。我們將在下一節詳細介紹happens-before原則。

synchronized關鍵字:保證有序性

synchronized關鍵字不僅可以保證原子性(將在下一節討論),也可以保證有序性。當一個線程進入synchronized塊時,它會獲取鎖;當它退出synchronized塊時,它會釋放鎖。synchronized塊內的代碼會作為一個原子操作執行,并且在釋放鎖之前,所有對共享變量的修改都會被刷新到主內存。同時,在獲取鎖之后,會從主內存中加載共享變量的最新值。這確保了synchronized塊內的操作不會被重排序到塊外,并且在不同線程之間,對同一個鎖的獲取和釋放操作之間存在happens-before關系。

volatile關鍵字:禁止指令重排序

除了保證可見性,volatile關鍵字的另一個重要作用是禁止指令重排序。具體來說,對于volatile變量的讀寫操作,編譯器和處理器會插入內存屏障(Memory Barrier),確保volatile寫操作之前的操作不會被重排序到volatile寫之后,并且volatile讀操作之后的指令不會被重排序到volatile讀之前。這有效地避免了上述ReorderingExample中可能出現的指令重排序問題。

3.3 原子性(Atomicity)

原子性是指一個操作是不可中斷的,要么全部執行成功,要么全部不執行,不會出現執行一半的情況。在多線程環境下,如果一個操作不是原子的,那么在執行過程中可能會被其他線程中斷,從而導致數據不一致。

問題示例:

經典的原子性問題是i++操作。i++看似是一個簡單的操作,但實際上它包含了三個步驟:

  1. 讀取i的值。
  2. i的值加1。
  3. 將新值寫回i

在多線程環境下,如果線程A讀取了i的值,但在將其加1并寫回之前,線程B也讀取了i的值并進行了加1操作,那么最終i的值可能不是預期的結果。

synchronized關鍵字:保證原子性

synchronized關鍵字是Java中實現原子性操作的主要方式。通過對代碼塊或方法使用synchronized,可以確保在同一時刻只有一個線程能夠執行被synchronized保護的代碼。這使得被保護的代碼塊成為一個原子操作,從而避免了并發問題。

使用示例:

public class AtomicExample {private int count = 0;public synchronized void increment() {count++;}public int getCount() {return count;}
}

increment方法上使用synchronized關鍵字,確保了count++操作的原子性,即使有多個線程同時調用increment方法,count的值也能正確地遞增。

4. happens-before 原則詳解

happens-before原則是Java內存模型中最重要的概念,它是判斷數據是否存在競爭、程序是否安全的主要依據。如果一個操作A happens-before另一個操作B,那么操作A的結果對操作B是可見的,并且操作A的執行順序先于操作B。以下是JMM中定義的一些happens-before規則:

  1. 程序次序規則(Program Order Rule):在一個線程內,按照程序代碼的順序,前面的操作happens-before后面的操作。這并不意味著實際執行順序必須與代碼順序一致,因為編譯器和處理器可能會進行指令重排序,但重排序后的結果必須與按程序順序執行的結果一致。

  2. 管程鎖定規則(Monitor Lock Rule):對一個管程(即synchronized塊或方法)的解鎖操作happens-before后續對這個管程的加鎖操作。這意味著,當一個線程釋放鎖時,它對共享變量的修改會刷新到主內存中,而當另一個線程獲取同一個鎖時,它會從主內存中加載共享變量的最新值。

  3. volatile變量規則(Volatile Variable Rule):對一個volatile變量的寫操作happens-before后續對這個volatile變量的讀操作。這保證了volatile變量的可見性,即一個線程對volatile變量的修改,對其他線程是立即可見的。

  4. 線程啟動規則(Thread Start Rule)Thread對象的start()方法happens-before此線程的每一個動作。這意味著,當調用Thread.start()方法啟動一個線程時,主線程在調用start()方法之前對共享變量的修改,對新啟動的線程是可見的。

  5. 線程終止規則(Thread Termination Rule):線程中的所有操作happens-before對此線程的終止檢測。我們可以通過Thread.join()方法檢測到線程已經終止,或者通過Thread.isAlive()的返回值等。

  6. 線程中斷規則(Thread Interruption Rule):對線程interrupt()方法的調用happens-before被中斷線程的代碼檢測到中斷事件的發生。可以通過Thread.interrupted()方法檢測到中斷。

  7. 對象終結規則(Finalizer Rule):一個對象的初始化完成happens-before它的finalize()方法的開始。

  8. 傳遞性(Transitivity):如果操作A happens-before操作B,并且操作B happens-before操作C,那么操作A happens-before操作C。這個規則使得happens-before關系具有傳遞性,可以推導出更多的happens-before關系。

這些規則共同構成了JMM的happens-before關系網,它們是Java并發編程中保證正確性的基石。理解并正確運用這些規則,可以幫助我們避免許多并發問題。

5. JMM中的同步機制

JMM通過一系列同步機制來幫助開發者編寫線程安全的并發程序。這些機制包括synchronized關鍵字、volatile關鍵字以及final關鍵字等。

5.1 synchronized 關鍵字

synchronized是Java中最基本的同步機制,它可以用于修飾方法或代碼塊。當一個線程訪問synchronized修飾的代碼時,它會先嘗試獲取對象的鎖。如果鎖已經被其他線程持有,當前線程就會被阻塞,直到獲取到鎖。當線程執行完synchronized代碼塊或方法后,它會釋放鎖。

作用:互斥與可見性

synchronized關鍵字主要有以下兩個作用:

  • 互斥性:確保在同一時刻只有一個線程能夠執行被synchronized保護的代碼,從而避免了多個線程同時修改共享變量導致的數據競爭問題。
  • 可見性synchronized的可見性是由“管程鎖定規則”保證的。當一個線程釋放鎖時,它對共享變量的修改會刷新到主內存中;當另一個線程獲取同一個鎖時,它會從主內存中加載共享變量的最新值。這確保了在synchronized塊內對共享變量的修改對其他線程是可見的。

使用示例:

public class SynchronizedExample {private int count = 0;// 修飾方法public synchronized void incrementMethod() {count++;}// 修飾代碼塊public void incrementBlock() {synchronized (this) { // 鎖定當前對象count++;}}public int getCount() {return count;}
}

在上述示例中,incrementMethod方法和incrementBlock方法都保證了count++操作的原子性和可見性。需要注意的是,synchronized鎖的是對象,而不是代碼。當修飾靜態方法時,鎖的是類的Class對象;當修飾非靜態方法或代碼塊時,鎖的是當前實例對象。

5.2 volatile 關鍵字

volatile關鍵字是JMM提供的另一種輕量級同步機制,它只能用于修飾變量。與synchronized不同,volatile不能保證原子性,但它能夠保證可見性和禁止指令重排序。

作用:可見性與禁止指令重排序

  • 可見性:如前所述,volatile變量的讀寫操作會直接與主內存進行交互,確保了對volatile變量的修改對所有線程都是立即可見的。
  • 禁止指令重排序volatile通過插入內存屏障來禁止特定類型的指令重排序。具體來說,volatile寫操作之前的所有操作都不能被重排序到volatile寫之后,volatile讀操作之后的所有操作都不能被重排序到volatile讀之前。這有效地避免了由于指令重排序導致的數據不一致問題。

使用示例與注意事項:

public class VolatileExample {private volatile boolean flag = false;public void writer() {// 操作1// ...flag = true; // volatile寫// 操作2// ...}public void reader() {// 操作3// ...if (flag) { // volatile讀// 操作4// ...}}
}

writer方法中,flag = true之前的操作(操作1)不會被重排序到flag = true之后,flag = true之后的操作(操作2)不會被重排序到flag = true之前。在reader方法中,if (flag)之后的代碼(操作4)不會被重排序到if (flag)之前。這保證了flag的可見性和操作的有序性。

volatile的適用場景:

volatile適用于以下兩種情況:

  1. 對變量的寫入操作不依賴于當前值:例如,一個狀態標志位,只需要簡單地設置為truefalse
  2. 該變量沒有包含在具有其他變量的不變式中:例如,如果一個變量的改變會影響到其他變量,那么僅僅使用volatile可能不足以保證線程安全,此時可能需要synchronized或其他更強大的同步機制。

5.3 final 關鍵字

final關鍵字在JMM中也扮演著重要的角色,它主要用于保證對象構造的可見性。

作用:保證對象構造的可見性

當一個對象被構造完成后,如果其final字段在構造函數中被正確初始化,那么在構造函數退出后,其他線程就能夠看到這些final字段的正確值,而無需額外的同步。JMM確保了在構造函數內對final字段的寫入,在構造函數退出后,對其他線程是可見的,并且不會被重排序到構造函數之外。

使用示例:

public class FinalExample {private final int value;public FinalExample() {value = 10; // 在構造函數中初始化final字段}public int getValue() {return value;}public static void main(String[] args) {FinalExample obj = new FinalExample();// 其他線程可以安全地讀取obj.value,因為它是final字段System.out.println(obj.getValue());}
}

通過final關鍵字,我們可以確保對象在發布(即構造函數執行完畢)后,其final字段的值是可見且不可變的,這對于創建不可變對象非常有用。

6. 實際案例分析

為了更好地理解JMM在實際并發編程中的應用,我們通過具體的案例來分析可見性問題和指令重排序問題,并展示如何利用JMM提供的機制來解決這些問題。

6.1 可見性問題及 volatile 解決方案

案例背景:

假設我們有一個簡單的計數器類,其中包含一個running標志,用于控制一個線程的啟動和停止。主線程啟動一個工作線程,該線程持續運行直到主線程將其停止。以下是一個沒有使用volatile關鍵字的示例:

public class CounterWithoutVolatile {private static int count = 0;private static boolean running = true;public static void main(String[] args) throws InterruptedException {Thread workerThread = new Thread(() -> {while (running) {count++;// 模擬一些工作try {Thread.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}System.out.println("Worker thread stopped. Count: " + count);});workerThread.start();// 主線程等待一段時間后停止工作線程Thread.sleep(1000);running = false; // 嘗試停止工作線程System.out.println("Main thread set running to false.");// 等待工作線程結束workerThread.join();System.out.println("Main thread finished.");}
}

在上述代碼中,running變量沒有被volatile修飾。當主線程將running設置為false時,這個修改可能僅僅發生在主線程的工作內存中,而沒有及時刷新到主內存。因此,workerThread可能仍然從自己的工作內存中讀取到running的舊值(true),導致while (running)循環無法終止,workerThread會一直運行下去,形成死循環。即使主線程已經將running設置為falseworkerThread也可能“看不見”這個變化。

volatile解決方案:

為了解決這個問題,我們只需要將running變量聲明為volatile即可:

public class CounterWithVolatile {private static volatile boolean running = true;private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread workerThread = new Thread(() -> {while (running) {count++;// 模擬一些工作try {Thread.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}System.out.println("Worker thread stopped. Count: " + count);});workerThread.start();Thread.sleep(1000);running = false; // volatile寫,立即刷新到主內存System.out.println("Main thread set running to false.");workerThread.join();System.out.println("Main thread finished.");}
}

通過volatile修飾running變量,當主線程修改running的值時,這個修改會立即被刷新到主內存。同時,workerThread在每次讀取running的值時,都會強制從主內存中加載最新值。這樣,workerThread就能及時感知到running變為false,從而正確地終止循環。

6.2 指令重排序問題及 happens-before 解決方案

案例背景:

指令重排序問題在單例模式的實現中尤為常見,特別是雙重檢查鎖定(Double-Checked Locking, DCL)模式。考慮以下不安全的DCL單例實現:

public class Singleton {private static Singleton instance;private Singleton() {// 構造函數}public static Singleton getInstance() {if (instance == null) { // 第一次檢查synchronized (Singleton.class) {if (instance == null) { // 第二次檢查instance = new Singleton(); // (1) 創建對象}}}return instance;}
}

問題分析:

instance = new Singleton() 這行代碼看似簡單,但實際上它包含了三個步驟:

  1. 分配對象的內存空間。
  2. 初始化對象。
  3. instance變量指向分配的內存地址。

在JVM中,步驟2和步驟3之間可能會發生指令重排序。也就是說,在某些情況下,步驟3可能會在步驟2之前執行。如果發生這種情況,當一個線程執行完步驟3(instance已經指向了內存地址,但對象尚未完全初始化)后,另一個線程進入getInstance()方法,并通過第一次instance == null檢查,發現instance不為null,直接返回了尚未完全初始化的instance對象。這會導致后續對該對象的使用出現問題。

volatile解決方案(結合happens-before):

為了解決DCL單例模式中的指令重排序問題,我們需要將instance變量聲明為volatile

public class SafeSingleton {private static volatile SafeSingleton instance;private SafeSingleton() {// 構造函數}public static SafeSingleton getInstance() {if (instance == null) {synchronized (SafeSingleton.class) {if (instance == null) {instance = new SafeSingleton(); // volatile寫,禁止指令重排序}}}return instance;}
}

通過將instance聲明為volatile,JMM會確保instance = new SafeSingleton()這行代碼的執行不會發生指令重排序。具體來說,volatile寫操作會插入內存屏障,保證在將instance指向內存地址之前,對象的初始化工作已經全部完成。這樣,當其他線程看到instance不為null時,它所指向的對象一定是完全初始化過的,從而保證了DCL單例模式的線程安全。

這個案例充分體現了volatile關鍵字在保證有序性方面的重要性,以及happens-before原則在理解并發行為中的指導作用。

7. 總結

Java內存模型(JMM)是Java并發編程的基石,它定義了線程如何通過內存進行交互,以及對共享變量的修改何時對其他線程可見。

我們了解到,可見性問題源于CPU緩存導致的數據不一致,可以通過volatile關鍵字解決;有序性問題源于編譯器和處理器的指令重排序,JMM通過happens-before原則和volatile關鍵字來保證;原子性則通過synchronized關鍵字來確保操作的不可中斷性。happens-before原則作為JMM的基石,為我們理解和分析并發程序的行為提供了強有力的理論依據。

正確使用JMM提供的同步機制是避免并發問題的關鍵。synchronized關鍵字提供了互斥性和可見性,適用于需要保證原子性操作的場景;volatile關鍵字則適用于只需要保證可見性和禁止指令重排序的場景,它是一種更輕量級的同步機制;final關鍵字則保證了對象構造的可見性。在實際開發中,我們應該根據具體的業務需求和性能考量,選擇合適的同步機制。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/89639.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/89639.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/89639.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

參數僅 12B! FLUX.1-Kontext-dev 實現高效文本驅動圖像編輯,性能媲美 GPT-4o

FLUX.1-Kontext-dev 是由 Black Forest Labs 團隊于 2025 年 6 月 26 日聯合發布的生成與編輯圖像的流匹配(flow matching)模型。FLUX.1 Kontext 的圖像編輯是廣泛意義上的圖像編輯,不僅支持圖像局部編輯(對圖像中的特定元素進行針…

Robot---能打羽毛球的機器人

1 前言 Robot系列主要介紹一些比較有意思的機器人,前面的博客文章中也給讀者朋友們展示了一些: 《人形機器人---越來越像人了》 《自動駕駛---兩輪自行車的自主導航》 《自動駕駛---會打架的“球形機器人”》 《Robot---SPLITTER行星探測機器人》 《Robo…

瀏覽器默認非安全端口列表

瀏覽器默認非安全端口列表: https://chromium.googlesource.com/chromium/src.git//refs/heads/master/net/base/port_util.cc 0, // Not in Fetch Spec.1, // tcpmux7, // echo9, // discard11, // systat13, // daytime15, // netstat17, …

在線租房平臺源碼+springboot+vue3(前后端分離)

大家好,今天給大家帶來一個非常完善的 在線租房平臺。大家可用學習下系統的設計和源碼風格。 視頻演示 在線租房平臺源碼springbootvue3 圖片演示 技術棧 后端 技術框架:JDK8SpringBoot Mybatis-Plus 數據庫:Mysql8 前端 核心框架 - Vue…

android核心技術摘要

Android APP 默認賦予權限apk簽名 apk簽名:https://blog.csdn.net/u014763302/article/details/149055647apksign使用反法:[https://www.cnblogs.com/fengxing999/p/11978037.html]從Android源碼生成系統簽名文件:https://blog.csdn.net/wenz…

離線遷移 Conda 環境到 Windows 服務器:用 conda-pack 擺脫硬路徑限制

背景: 在進行深度學習算法的環境部署時,由于生產服務器被徹底隔離外網,只能把conda中env文件夾中的虛擬環境文件夾從開發機直接拷到離線 Windows 服務器。 其中一運行labelimg就報錯: Fatal error in launcher: Unable to creat…

vue3+原生javascript 手寫日期時間選擇框 滾動選擇可輸入

需求: web端 想要跟手機端一樣選擇年月日時分,通過滾動選擇 實現效果圖: 理念: 1.年月日時分 分別為單個輸入框,用來做輸入修改 2.div把輸入框拼接起來,顯示出一個日期框的樣子 3.年月日時分 下拉給默…

Jetson邊緣計算主板:Ubuntu 環境配置 CUDA 與 cudNN 推理環境 + OpenCV 與 C++ 進行目標分類

最近由于業務需求,接觸到了Jetson邊緣AI計算主板,博主使用的是Jetson Orin NX 16GB這個版本,可以看到其算力達到了100TOPS,這是一個非常恐怖的算力了,接下來便是博主對其的環境配置過程,博主要在該主板上運…

CLIP模型實現中的其他細節

之前已經完整的拆解了CLIP中所用到的ResNet、ViT和Transformer三個模型(CLIP拆解-CSDN博客),這篇將講解model.py實現中的其他細節。 1.關于ResNet模型中vision_head的設置 ResNet: vision_heads vision_width * 32 // 64 ViT: vision_h…

國科大深度學習作業1-手寫數字識別實驗

背景介紹:單位實習,趁機摸魚,由于電腦只安裝了VSCode,所以算是從環境搭建寫起。 目錄 一、環境搭建 1. 安裝Anaconda 2. 創建Python環境 3. 安裝PyTorch 4. 安裝其他必要庫 二、在 VSCode 中配置環境 1. 安裝Pytho…

基于Spring Boot的綠園社區團購系統的設計與實現

第1章 摘 要 本設計與實現的基于Spring Boot的綠園社區團購系統,旨在為社區居民提供一套高效、便捷的團購購物解決方案。隨著電子商務的發展和社區居民對便捷購物需求的增加,傳統的團購模式已無法滿足用戶的個性化需求。本系統通過整合現代化技術&…

【51單片機四位數碼管從0循環顯示到99,每0.5秒增加一個數字,打擊鍵計數】2022-6-11

緣由 #include "REG52.h" unsigned char code smgduan[]{0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,64,15,56}; //共陰0~F消隱減號 unsigned char Js0, miao0;//中斷計時 秒 分 時 毫秒 unsigned int shu0; //bit Mb0;//…

如何通過python腳本向redis和mongoDB傳點位數據

向MongoDB傳數據 from pymongo import MongoClient #導入庫對應的庫localhost "172.16.0.203" #數據庫IP地址 baseName "GreenNagoya" client MongoClient(localhost, 27017, username"admin", password"zdiai123") #數…

昆侖通泰觸摸屏Modbus TCP服務器工程 || TCP客戶端工程

目錄 一、Modbus TCP服務端 1.設備地址 2.實操及數據 二、Modbus TCP客戶端 1.結果及協議解析 一、Modbus TCP服務端 1.設備地址 --單元標識符 DI輸入/4個離散輸入 DO輸出/單個線圈輸出 輸入寄存器 讀輸入寄存器操作,寫輸入寄存器操作 保持寄存器 …

PyTorch 安裝使用教程

一、PyTorch 簡介 PyTorch 是由 Facebook AI Research 團隊開發的開源深度學習框架。它以動態圖機制、靈活性強、易于調試而著稱,廣泛應用于自然語言處理、計算機視覺和學術研究。 二、安裝 PyTorch 2.1 通過官網選擇安裝命令(推薦) 訪問官…

開源功能開關(feature flags) 和管理平臺之unleash

文章目錄 背景Flagsmith 和 Unleash什么是unleash架構Unleash Edge 安裝和使用Unleash SDKs開放API Tokens訪問**Server-side SDK (CLIENT)****查詢所有 Feature Toggles****查詢特定 Toggle** API token typesClient tokensFrontend tokensPersonal access tokensService acco…

細胞建模“圖靈測試”:解析學習虛擬細胞挑戰賽

一、AI能否預測細胞的未來? 想象一下,有一天我們不必一管管地做實驗,就能在計算機中模擬細胞對基因敲除、藥物處理乃至微環境變化的反應。這不再是科幻,而是“虛擬細胞”(Virtual Cell)研究的宏大目標。然…

centos9安裝docker Dify

CentOS | Docker Docs yum -y install gcc gcc-c yum-utils Docker 官方的 YUM 軟件倉庫配置文件到系統,設置存儲庫 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 也可以從阿里云下(我選擇上面的) yum-config-manager --add-re…

基于Jenkins和Kubernetes構建DevOps自動化運維管理平臺

目錄 引言 基礎概念 DevOps概述 Jenkins簡介 Kubernetes簡介 Jenkins與Kubernetes的關系 Jenkins與Kubernetes的集成 集成架構 安裝和配置 安裝Jenkins 安裝Kubernetes插件 配置Kubernetes連接 配置Jenkins Agent Jenkins Pipeline與Kubernetes集成 Pipeline定義…

MySQL 8.0 OCP 1Z0-908 題目解析(18)

題目69 Choose three. A MySQL server is monitored using MySQL Enterprise Monitor’s agentless installation. Which three features are available with this installation method? □ A) MySQL Replication monitoring □ B) security-related advisor warnings □ …