synchronized關鍵字詳解

文章目錄

    • synchronized
      • 使用示例
      • 實現原理
      • 鎖的升級
      • synchronized與可見性
      • synchronized與原子性
      • synchronized與有序性

synchronized

synchronized是Java提供的關鍵字譯為同步,是Java中用于實現線程同步的一種機制。它可以確保在同一時間只有一個線程能夠執行某段代碼,從而避免線程安全問題。當它修飾一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執行這段代碼。synchronized關鍵字在需要原子性、可見性和有序性這三種特性的時候都可以作為其中一種解決方案,大部分并發控制操作都能使用synchronized來完成。

synchronized的作用:

  • 互斥性:確保在同一時間只有一個線程可以執行被 synchronized 修飾的代碼塊或方法。
  • 可見性:當一個線程退出 synchronized 代碼塊時,它所做的所有修改對于進入 synchronized 代碼塊的其他線程是可見的。這是通過釋放和獲得監視器鎖來實現的。

使用示例

修飾的對象作用范圍作用對象
同步一個實例方法整個實例方法調用此方法的對象
同步一個靜態方法整個靜態方法此類的所有對象
同步代碼塊-對象整個代碼塊調用此代碼塊的對象
同步代碼塊-類整個代碼塊此類的所有對象
  • 同步一個實例方法。在這種情況下,increment方法被聲明為同步方法。當一個線程調用這個方法時,它會獲得該實例的監視器鎖,其他線程必須等待這個線程釋放鎖后才能調用這個方法。
    public synchronized void increment() {count++;
    }
    
  • 同步一個靜態方法。當synchronized作用于靜態方法時,其鎖就是當前類的class對象鎖。由于靜態成員不專屬于任何一個實例對象,而是類成員,因此通過class對象鎖可以控制靜態成員的并發操作。
    public static synchronized void increment() {count++;
    }
    
  • 同步代碼塊。在某些情況下,我們編寫的方法體可能比較大,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分,如果直接對整個方法進行同步操作,這樣做就有點浪費。此時我們可以使用同步代碼塊的方式對需要同步的代碼進行包裹。
    public void increment() {synchronized (this) {count++;}
    }
    
    除了使用synchronized(this)鎖定,當然靜態方法是沒有this對象的,也可以使用class對象來做為鎖。
    public void increment() {synchronized (MainTest.class) {count++;}
    }
    

當如果沒有明確的對象作為鎖,只是想讓一段代碼同步時,可以創建一個特殊的對象來充當鎖。

private byte[] lock = new byte[0];
public void method(){synchronized(lock) {// .....}
}

零長度的byte數組對象創建起來將比任何對象都經濟。查看編譯后的字節碼,生成零長度的byte[]對象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。

byte[] emptyArray = new byte[0];0: iconst_0       // 將常量0推送到棧頂
1: newarray byte  // 創建一個新的byte類型數組
3: astore_1       // 將引用類型的數據存儲到局部變量表中
Object lock = new Object();0: new           #2   // 創建一個新的對象
3: dup                // 復制棧頂的操作數棧頂的值,并將復制值壓入棧頂
4: invokespecial #1   // 調用實例初始化方法, 使用Object.<init>
7: astore_1           // 將引用類型的數據存儲到局部變量表中

實現原理

synchronized關鍵字在Java中通過進入和退出一個監視器來實現同步。監視器本質上是一種鎖,它可以是類對象鎖或實例對象鎖。每個對象在JVM中都有一個與之關聯的監視器。當一個線程進入同步代碼塊或方法時,它會嘗試獲得對象的監視器。如果成功獲得鎖,線程就可以執行同步代碼;否則它將被阻塞,直到獲得鎖為止。

在Java中synchronized鎖對象時,其實就是改變對象中的對象頭的markword的鎖的標志位來實現的。用javap -v MainTest.class命令反編譯下面代碼。

public class MainTest {synchronized void demo01() {System.out.println("demo 01");}void demo02() {synchronized (MainTest.class) {System.out.println("demo 02");}}}
  synchronized void demo01();descriptor: ()Vflags: ACC_SYNCHRONIZEDCode:stack=2, locals=1, args_size=10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #3                  // String demo 015: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return
// ...
void demo02();descriptor: ()Vflags:Code:stack=2, locals=3, args_size=10: ldc           #5                  // class content/posts/rookie/MainTest2: dup3: astore_14: monitorenter5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc           #6                  // String demo 0210: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto          2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: return
// ...

通過反編譯后代碼可以看出:

  • 對于同步方法,JVM采用ACC_SYNCHRONIZED標記符來實現同步;
  • 對于同步代碼塊,JVM采用monitorentermonitorexit兩個指令來實現同步;

其中同步代碼塊,有兩個monitorexit指令的原因是為了保證拋異常的情況下也能釋放鎖,所以javac為同步代碼塊添加了一個隱式的try-finally,在finally中會調用monitorexit命令釋放鎖。

官方文檔中關于同步方法和同步代碼塊的實現原理描述:

方法級的同步是隱式的。同步方法的常量池中會有一個 ACC_SYNCHRONIZED 標志。當某個線程要訪問某個方法的時候,會檢查是否有 ACC_SYNCHRONIZED,如果有設置,則需要先獲得監視器鎖,然后開始執行方法,方法執行之后再釋放監視器鎖。這時如果其他線程來請求執行方法,會因為無法獲得監視器鎖而被阻斷住。值得注意的是,如果在方法執行過程中,發生了異常,并且方法內部并沒有處理該異常,那么在異常被拋到方法外面之前監視器鎖會被自動釋放。

同步代碼塊使用 monitorentermonitorexit 兩個指令實現。可以把執行 monitorenter 指令理解為加鎖,執行 monitorexit 理解為釋放鎖。 每個對象維護著一個記錄著被鎖次數的計數器。未被鎖定的對象的該計數器為0,當一個線程獲得鎖(執行 monitorenter)后,該計數器自增變為 1 ,當同一個線程再次獲得該對象的鎖的時候,計數器再次自增。當同一個線程釋放鎖(執行 monitorexit 指令)的時候,計數器再自減。當計數器為0的時候。鎖將被釋放,其他線程便可以獲得鎖。

其實無論是ACC_SYNCHRONIZED還是monitorentermonitorexit都是基于Monitor實現的,每一個鎖都對應一個monitor對象。在Java虛擬機(HotSpot)中,Monitor是基于C++實現的,由ObjectMonitor實現。在/hotspot/src/share/vm/runtime/objectMonitor.hpp中有ObjectMonitor的實現。

// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {_header       = NULL;_count        = 0; //記錄個數_waiters      = 0,_recursions   = 0;_object       = NULL;_owner        = NULL;_WaitSet      = NULL; //處于wait狀態的線程,會被加入到_WaitSet_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ; //處于等待鎖block狀態的線程,會被加入到該列表_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;}
  • _owner:指向持有ObjectMonitor對象的線程;
  • _WaitSet:存放處于wait狀態的線程隊列;
  • _EntryList:存放處于等待鎖block狀態的線程隊列;
  • _recursions:鎖的重入次數;
  • _count:用來記錄該線程獲取鎖的次數;

當多個線程同時訪問一段同步代碼時,首先會進入_EntryList隊列中,當某個線程獲取到對象的monitor后進入_Owner區域,并把monitor中的_owner變量設置為當前線程,同時monitor中的計數器_count加1,即獲得對象鎖。

在這里插入圖片描述

若此時持有monitor的線程調用wait()方法,將釋放當前對象持有的monitor_owner變量恢復為null_count自減1,同時該線程進入_WaitSet集合中等待被喚醒。若當前線程執行完畢也將釋放monitor并復位變量的值,以便其他線程進入獲取monitor

ObjectMonitor中其他方法:

  bool      try_enter (TRAPS) ;void      enter(TRAPS);void      exit(bool not_suspended, TRAPS);void      wait(jlong millis, bool interruptable, TRAPS);void      notify(TRAPS);void      notifyAll(TRAPS);

sychronized加鎖的時候,會調用objectMonitorenter方法,解鎖的時候會調用exit方法。在JDK1.6之前,synchronized的實現直接調用ObjectMonitorenterexit,這種鎖被稱之為重量級鎖,這也是早期synchronized效率低的原因。所以,在JDK1.6中出現對鎖進行了很多的優化,進而出現輕量級鎖,偏向鎖,鎖消除,適應性自旋鎖,鎖粗化。

早期的synchronized效率低的原因:
Java的線程是映射到操作系統原生線程之上的,如果要阻塞或喚醒一個線程就需要操作系統的幫忙,監視器鎖monitor是依賴于底層的操作系統的Mutex Lock來實現的,而操作系統實現線程之間的切換時需要從用戶態轉換到核心態。因此狀態轉換需要花費很多的處理器時間。

對于代碼簡單的同步塊(如被synchronized修飾的getset方法)狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長,所以說synchronized是Java語言中一個重量級的操作。也是為什么早期的synchronized效率低的原因。

鎖的升級

在JDK1.6之前,使用synchronized被稱作重量級鎖,它的實現是基于底層操作系統的mutex互斥原語的,這個開銷是很大的。所以在JDK1.6時JVM對synchronized做了優化。synchronized鎖對象時,其實就是改變對象中的對象頭的markword的鎖的標志位來實現的。對象頭中markword鎖狀態的表示:

鎖狀態markword 鎖標志位
無鎖狀態01
偏向鎖狀態01
輕量級鎖狀態00
重量級鎖狀態10
被垃圾回收器標記11

對象的鎖狀態,可以分為4種,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態。其中這幾個鎖只有重量級鎖是需要使用操作系統底層mutex互斥原語來實現,其他的鎖都是使用對象頭來實現的。

  • 無鎖狀態:markword鎖的標志位0,偏向鎖的標志位為1;例如:剛被創建出來的對象。
  • 偏向鎖:如果一個線程獲取了鎖,此時markword的結構變為偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,直接可以獲取鎖。
    省去了大量有關鎖申請的操作,從而也就提供程序的性能。
  • 輕量級鎖:當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞從而提高性能。
  • 重量級鎖:升級為重量級鎖時,鎖標志的狀態值變為“10”,此時MarkWord中存儲的是指向重量級鎖的指針,此時等待鎖的線程都會進入阻塞狀態,所以開銷是很大。

隨著鎖的競爭,鎖從偏向鎖升級到輕量級鎖,再升級的重量級鎖。鎖升級過程:

  1. 無鎖狀態升級為偏向鎖:一個對象剛開始實例化的時候,沒有任何線程來訪問它的時候,它是可偏向的,意味著它現在認為只可能有一個線程來訪問它,所以當第一個線程來訪問它的時候,它會偏向這個線程。此時對象持有偏向鎖。偏向第一個線程,這個線程在修改對象頭成為偏向鎖的時候使用CAS操作,并將對象頭中的ThreadID改成自己的ID,之后再次訪問這個對象時,只需要對比ID,就不需要再使用CAS在進行操作。
  2. 偏向鎖升級為輕量級鎖:一旦有第二個線程訪問這個對象,因為偏向鎖不會主動釋放,所以第二個線程可以看到對象的偏向狀態。這時表明在這個對象上已經存在競爭了,JVM會檢查原來持有該對象鎖的線程是否依然存活,如果不存活,則可以將對象變為無鎖狀態,然后重新偏向新的線程。如果原來的線程依然存活,則馬上執行這個線程的操作棧,檢查該對象的使用情況,如果仍然需要持有偏向鎖,則偏向鎖升級為輕量級鎖。
  3. 輕量級鎖升級為重量級鎖:輕量級鎖認為競爭存在,但是競爭的程度很輕,一般兩個線程對于同一個鎖的操作都會錯開,或者說稍微等待一下,另一個線程就會釋放鎖。但是當自旋超過一定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹為重量級鎖,重量級鎖使除了擁有鎖的線程以外的線程都阻塞。當持有鎖的線程退出同步塊或方法時,會執行monitorexit指令釋放鎖。如果有其他線程在等待該鎖,它們會被喚醒并競爭鎖的所有權。

在所有的鎖都啟用的情況下,線程進入臨界區時會先獲取偏向鎖,如果已經存在偏向鎖了,則會嘗試獲取輕量級鎖,啟用自旋鎖。如果自旋也沒有獲取到鎖,則使用重量級鎖,將沒有獲取到鎖的線程阻塞掛起,直到持有鎖的線程執行完同步塊喚醒他們。

偏向鎖是在無鎖爭用的情況下使用的,也就是同步代碼塊在當前線程沒有執行完之前,沒有其它線程會執行該同步塊。一旦有了第二個線程的爭用,偏向鎖就會升級為輕量級鎖,如果輕量級鎖自旋到達閾值后,沒有獲取到鎖,就會升級為重量級鎖。

鎖可以升級,但是不可以降級,有的觀點認為不會進行鎖降級。實際上,鎖降級確實是會發生的,當JVM進入安全點的時候,會檢查是否有閑置的`Monitor,然后試圖進行降級。也就是說,僅僅是發生在STW的時候,只有垃圾回收線程能夠觀測到它,在我們正常使用的過程中是不會發生鎖降級的,只有在GC的時候才會降級。

安全點:程序執行時并非在所有地方都能停頓下來開始GC,只有在特定的位置才能停頓下來開始GC,這些位置稱為安全點。

synchronized與可見性

可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。

Java內存模型規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存。不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要自己的工作內存和主存之間進行數據同步進行。所以就可能出現線程1改了某個變量的值,但是線程2不可見的情況。

synchronized修飾的代碼,在開始執行時會加鎖,執行完成后會進行解鎖。但是為了保證可見性,有一條規則是這樣的,“對一個變量解鎖之前,必須先把此變量同步回主存中”,這樣解鎖后,后續線程就可以訪問到被修改后的值。所以synchronized關鍵字鎖住的對象,其值是具有可見性的。

public class VisibilityExample {private boolean flag = false;public synchronized void toggleFlag() {// 修改共享變量并確保可見性flag = !flag;// 其他操作}public synchronized boolean isFlag() {// 讀取共享變量并確保可見性return flag;}
}

synchronized與原子性

原子性是指一個操作是不可中斷的,要全部執行完成,要不就都不執行。

線程是CPU調度的基本單位,CPU有時間片的概念,會根據不同的調度算法進行線程調度。當一個線程獲得時間片之后開始執行,在時間片耗盡之后,就會失去CPU使用權。所以在多線程場景下,由于時間片在線程間輪換,就會發生原子性問題。在Java中,為了保證原子性,提供了兩個高級的字節碼指令monitorentermonitorexit,這兩個字節碼指令,在Java中對應的關鍵字就是synchronized。通過monitorexitmonitorexit指令,可以保證被synchronized修飾的代碼在同一時間只能被一個線程訪問,在鎖未釋放之前,無法被其他線程訪問到。因此在Java中可以使用synchronized來保證方法和代碼塊內的操作是原子性的。

舉個例子,線程1在執行monitorenter指令的時候,會對Monitor進行加鎖,加鎖后其他線程無法獲得鎖,除非線程1主動解鎖。即使在執行過程中,由于某種原因,比如CPU時間片用完,線程1放棄了CPU,但是它并沒有進行解鎖。而由于synchronized的鎖是可重入的,下一個時間片還是只能被他自己獲取到,還是會繼續執行代碼,直到所有代碼執行完,這就保證了原子性。

public class AtomicityExample {private int count = 0;public synchronized void increment() {// 原子性的遞增操作count++;}public synchronized void decrement() {// 原子性的遞減操作count--;}public synchronized int getCount() {// 原子性的讀取操作return count;}public static void main(String[] args) {AtomicityExample example = new AtomicityExample();// 線程1:遞增操作Thread thread1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.increment();}});// 線程2:遞減操作Thread thread2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {example.decrement();}});// 啟動線程thread1.start();thread2.start();try {// 等待兩個線程執行完成thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 輸出最終的計數結果System.out.println("Final Count: " + example.getCount());}
}

synchronized與有序性

有序性即程序執行的順序按照代碼的先后順序執行。

除了引入了時間片以外,由于處理器優化和指令重排等,CPU還可能對輸入代碼進行亂序執行,比如load->add->save有可能被優化成load->save->add這就是可能存在有序性問題。這里需要注意的是,synchronized是無法禁止指令重排和處理器優化的,也就是說synchronized無法避免上述提到的問題。那synchronized是如何保證有序性的?

synchronized通過兩個主要機制來保證有序性。synchronized的主要特性是互斥性,意味著在同一時刻只有一個線程可以進入同步塊,既然是單線程就需要遵守as-if-serial語義,那么就可以認為單線程程序是按照順序執行的。

as-if-serial語義:不管怎么重排序(編譯器和處理器為了提高并行度),單線程程序的執行結果都不能被改變。編譯器和處理器無論如何優化,都必須遵守as-if-serial語義。

第二個保證就是內存屏障。編譯器和CPU在執行代碼時,可能會為了優化性能進行指令重排,但synchronized塊內的指令不會被重排。原因就是Java內存模型通過在進入和退出synchronized塊時插入內存屏障,來保證這些操作在多線程環境下的順序執行。在進入synchronized塊時,會插入一個LoadLoad屏障和一個LoadStore屏障,確保在鎖被獲取后,前面的所有讀操作和寫操作都已經完成。在退出synchronized塊時,會插入一個StoreStore屏障和一個StoreLoad屏障,確保在鎖被釋放前,所有的寫操作都已經完成,并且這些寫操作對其他線程可見。

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

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

相關文章

【Python系列】數字的bool值

&#x1f49d;&#x1f49d;&#x1f49d;歡迎來到我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:kwan 的首頁,持續學…

泌尿系統疾病病人的護理

一、泌尿系統疾病病人的一般護理要點 對于泌尿系統疾病的病人&#xff0c;護理是至關重要的。首先&#xff0c;要密切觀察病人的生命體征&#xff0c;包括體溫、脈搏、呼吸和血壓。 飲食方面&#xff0c;應根據病人的具體病情進行調整。例如&#xff0c;對于有水腫的病人&#…

js登陸驗證

當開始制作網頁時&#xff0c;就需要做一個判斷&#xff0c;不管在第幾頁進入&#xff0c;都要加一個登陸驗證&#xff0c;只有用戶有賬號&#xff0c;才能進入網頁&#xff0c;瀏覽網頁信息。下面就來看一下&#xff0c;使用JavaScript幾行代碼實現登陸驗證。 首先 登錄頁是i…

vue父組件樣式穿透修改子組件樣式

在 Vue 中&#xff0c;使用父組件樣式穿透到子組件通常不推薦&#xff0c;因為它破壞了樣式的作用域隔離&#xff0c;但如果你確實需要這樣做&#xff0c;可以使用深度選擇器。Vue 2 使用 ::v-deep&#xff0c;而 Vue 3 使用 /deep/ 或 ::v-deep 都可以。 以下是使用深度選擇器…

MVC之 IHttpModule管道模型《二》

》》》注意&#xff1a;在http請求的處理過程中&#xff0c;只能調用一個HttpHandler&#xff0c;但可以調用多個HttpModule。 HTTP Modules ASP.NET請求處理過程是基于管道模型的&#xff0c;這個管道模型是由多個HttpModule和HttpHandler組成&#xff0c;當請求到達HttpMod…

java-mysql-insert 操作

在 Java 中&#xff0c;使用 JDBC 插入數據到 MySQL 數據庫是非常常見的操作。以下是一個詳細的步驟&#xff0c;展示如何使用 JDBC 插入數據到 MySQL 數據庫。 ### 一、準備工作 #### 1. 下載并安裝 MySQL 如果您還沒有安裝 MySQL&#xff0c;可以從 MySQL 官方網站下載并安…

UART編程

Q:為什么使用串口前要先在電腦上安裝CH340驅動&#xff1f; 中斷的作用&#xff1f; 環形buffer的作用&#xff1f; static和valitate的作用 三種編程方式簡介 也可以通過DMA方式減小CPU資源的消耗 直接把數據在SRAM內存和UART模塊進行傳輸 &#xff0c;流程&#xff1a; …

玩家自行定制內存將古老的386 PC內存升級到64MB容量

比爾蓋茨曾說&#xff1a;“無論對誰來說&#xff0c;640K內存都足夠了。” 如果你是一個還停留在 30 針 SIMM 時代的老式電腦愛好者&#xff0c;那么你的內存升級選擇是相當有限的。不過&#xff0c;YouTube 上的一個頻道已經展示了如何將古老的 386 系統內存升級到令人"…

【AI應用探討】—對抗學習(AL)應用場景

目錄 一、圖像領域 二、自然語言處理&#xff08;NLP&#xff09; 三、安全領域 四、其他領域 五、醫療健康領域 六、游戲與娛樂領域 七、機器人與自動化領域 八、科研與教育領域 九、物聯網與邊緣計算 十、金融科技 十一、能源與環境 十二、社會科學與人文研究 十…

Linux內核編譯安裝 - Deepin,Debian系

為什么要自己編譯內核 優點 定制化&#xff1a;你可以根據自己的硬件和需求配置內核&#xff0c;去掉不必要的模塊&#xff0c;優化性能。性能優化&#xff1a;移除不需要的驅動程序和特性&#xff0c;減小內核體積&#xff0c;提高系統性能。最新特性和修復&#xff1a;獲取…

什么是開放最短路徑優先(OSPF)

OSPF是一種典型的鏈路狀態路由協議&#xff0c;一般在同一個路由域中使用。這里的路由域指的是一個自治系統&#xff08;AS&#xff09;&#xff0c;是指一組通過統一的路由策略或協議相互交換路由信息的網絡。在這個自治系統&#xff08;AS&#xff09;中&#xff0c;所有的OS…

【Leetcode】最小數字游戲

你有一個下標從 0 開始、長度為 偶數 的整數數組 nums &#xff0c;同時還有一個空數組 arr 。Alice 和 Bob 決定玩一個游戲&#xff0c;游戲中每一輪 Alice 和 Bob 都會各自執行一次操作。游戲規則如下&#xff1a; 每一輪&#xff0c;Alice 先從 nums 中移除一個 最小 元素&…

等保測評是做什么的

等保測評的定義和目的 等保測評&#xff0c;全稱為信息安全等級保護測評&#xff0c;是依據國家信息安全等級保護規范規定&#xff0c;由具有資質的測評機構對信息系統安全等級保護狀況進行檢測評估的活動。等保測評的目的是驗證網絡系統或應用是否滿足相應的安全保護等級要求…

【Linux】System V消息隊列 System V信號量

&#x1f466;個人主頁&#xff1a;Weraphael ?&#x1f3fb;作者簡介&#xff1a;目前正在學習c和算法 ??專欄&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起進步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指點一二 如果文章對…

前端 JS 經典:小數運算不精確

原因&#xff1a;計算機對小數的存儲是二進制的&#xff0c;有限位數的二進制做算法得到的是有限位數&#xff0c;無限位數的二進制做運算&#xff0c;得到的是無限位數。 如下&#xff1a;0.5 和 0.25 轉二進制是有限位數&#xff0c;0.3 和 0.2 轉二進制是無限位數。 (0.5)…

Spark調度底層執行原理詳解(第35天)

系列文章目錄 一、Spark應用程序啟動與資源申請 二、DAG&#xff08;有向無環圖&#xff09;的構建與劃分 三、Task的生成與調度 四、Task的執行與結果返回 五、監控與容錯 六、優化策略 文章目錄 系列文章目錄前言一、Spark應用程序啟動與資源申請1. SparkContext的創建2. 資…

力扣1111.有效括號的嵌套深度

力扣1111.有效括號的嵌套深度 棧模擬 對于每個括號求出深度 奇數深度存入A&#xff0c;偶數深度存入B這樣最大程度降低最大深度 class Solution {public:vector<int> maxDepthAfterSplit(string s) {//因為棧中只會存(的數量 所有用一個變量即可int d 0;vector<i…

Python | Leetcode Python題解之第233題數字1的個數

題目&#xff1a; 題解&#xff1a; class Solution:def countDigitOne(self, n: int) -> int:# mulk 表示 10^k# 在下面的代碼中&#xff0c;可以發現 k 并沒有被直接使用到&#xff08;都是使用 10^k&#xff09;# 但為了讓代碼看起來更加直觀&#xff0c;這里保留了 kk,…

C語言內存管理深度解析面試題及參考答案(2萬字長文)

在嵌入式面試時,C語言內存管理是必問面試題,也是難點,相關知識點可以參考: C語言內存管理深度解析??????? 下面整理了各種類型的C語言內存管理的面試題: 目錄 全局變量和局部變量在內存中分別存儲在哪個區域? 靜態變量和全局變量有什么區別? 什么是作用域?…

ORM Bee,如何使用Oracle的TO_DATE函數?

ORM Bee,如何使用Oracle的TO_DATE函數? 在Bee V2.4.0,可以這樣使用: LocaldatetimeTable selectBeannew LocaldatetimeTable();Condition conditionBF.getCondition();condition.op("localdatetime", Op.ge, new TO_DATE("2024-07-08", "YYYY-MM-DD&…