深入理解 java synchronized 關鍵字

🧑 博主簡介:CSDN博客專家歷代文學網(PC端可以訪問:https://literature.sinhy.com/#/literature?__c=1000,移動端可微信小程序搜索“歷代文學”)總架構師,15年工作經驗,精通Java編程高并發設計Springboot和微服務,熟悉LinuxESXI虛擬化以及云原生Docker和K8s,熱衷于探索科技的邊界,并將理論知識轉化為實際應用。保持對新技術的好奇心,樂于分享所學,希望通過我的實踐經歷和見解,啟發他人的創新思維。在這里,我希望能與志同道合的朋友交流探討,共同進步,一起在技術的世界里不斷學習成長。
技術合作請加本人wx(注明來自csdn):foreast_sea

在這里插入圖片描述


在這里插入圖片描述

文章目錄

  • 深入理解 synchronized 關鍵字
    • 前言
    • 淺析 synchronized
    • synchronized 的使用
      • synchronized 修飾實例方法
      • synchronized 修飾靜態方法
      • synchronized 修飾代碼塊
    • synchronized 底層原理
      • Monitor 對象
      • 對象內存布局
        • 對象頭 Header
        • 實例數據 Instance Data
        • 對齊 Padding
    • 鎖的升級流程
      • 無鎖
      • 偏向鎖
      • 輕量級鎖
      • 重量級鎖
    • synchronized 代碼塊的底層實現
    • synchronized 修飾方法的底層原理

深入理解 synchronized 關鍵字

前言

synchronized 這個關鍵字的重要性不言而喻,幾乎可以說是并發、多線程必須會問到的關鍵字了。synchronized 會涉及到鎖、升級降級操作、鎖的撤銷、對象頭等。所以理解 synchronized 非常重要,本篇文章就帶你從 synchronized 的基本用法、再到 synchronized 的深入理解,對象頭等,為你揭開 synchronized 的面紗。

淺析 synchronized

synchronized 是 Java 并發模塊非常重要的關鍵字,它是 Java 內建的一種同步機制,代表了某種內在鎖定的概念,當一個線程對某個共享資源加鎖后,其他想要獲取共享資源的線程必須進行等待,synchronized 也具有互斥和排他的語義。

什么是互斥?我們想必小時候都玩兒過磁鐵,磁鐵會有正負極的概念,同性相斥異性相吸,相斥相當于就是一種互斥的概念,也就是兩者互不相容。

synchronized 也是一種獨占的關鍵字,但是它這種獨占的語義更多的是為了增加線程安全性,通過獨占某個資源以達到互斥、排他的目的。

在了解了排他和互斥的語義后,我們先來看一下 synchronized 的用法,先來了解用法,再來了解底層實現。

synchronized 的使用

關于 synchronized 想必你應該都大致了解過

  • synchronized 修飾實例方法,相當于是對類的實例進行加鎖,進入同步代碼前需要獲得當前實例的鎖
  • synchronized 修飾靜態方法,相當于是對類對象進行加鎖
  • synchronized 修飾代碼塊,相當于是給對象進行加鎖,在進入代碼塊前需要先獲得對象的鎖

下面我們針對每個用法進行解釋

synchronized 修飾實例方法

synchronized 修飾實例方法,實例方法是屬于類的實例。synchronized 修飾的實例方法相當于是對象鎖。下面是一個 synchronized 修飾實例方法的例子。

public synchronized void method()
{// ...
}

像如上述 synchronized 修飾的方法就是實例方法,下面我們通過一個完整的例子來認識一下 synchronized 修飾實例方法

public class TSynchronized implements Runnable{static int i = 0;public synchronized void increase(){i++;System.out.println(Thread.currentThread().getName());}@Overridepublic void run() {for(int i = 0;i < 1000;i++) {increase();}}public static void main(String[] args) throws InterruptedException {TSynchronized tSynchronized = new TSynchronized();Thread aThread = new Thread(tSynchronized);Thread bThread = new Thread(tSynchronized);aThread.start();bThread.start();aThread.join();bThread.join();System.out.println("i = " + i);}
}

上面輸出的結果 i = 2000 ,并且每次都會打印當前現成的名字

來解釋一下上面代碼,代碼中的 i 是一個靜態變量,靜態變量也是全局變量,靜態變量存儲在方法區中。increase 方法由 synchronized 關鍵字修飾,但是沒有使用 static 關鍵字修飾,表示 increase 方法是一個實例方法,每次創建一個 TSynchronized 類的同時都會創建一個 increase 方法,increase 方法中只是打印出來了當前訪問的線程名稱。Synchronized 類實現了 Runnable 接口,重寫了 run 方法,run 方法里面就是一個 0 - 1000 的計數器,這個沒什么好說的。在 main 方法中,new 出了兩個線程,分別是 aThread 和 bThread,Thread.join 表示等待這個線程處理結束。這段代碼主要的作用就是判斷 synchronized 修飾的方法能夠具有獨占性。

synchronized 修飾靜態方法

synchronized 修飾靜態方法就是 synchronized 和 static 關鍵字一起使用

public static synchronized void increase(){}

當 synchronized 作用于靜態方法時,表示的就是當前類的鎖,因為靜態方法是屬于類的,它不屬于任何一個實例成員,因此可以通過 class 對象控制并發訪問。

這里需要注意一點,因為 synchronized 修飾的實例方法是屬于實例對象,而 synchronized 修飾的靜態方法是屬于類對象,所以調用 synchronized 的實例方法并不會阻止訪問 synchronized 的靜態方法。

synchronized 修飾代碼塊

synchronized 除了修飾實例方法和靜態方法外,synchronized 還可用于修飾代碼塊,代碼塊可以嵌套在方法體的內部使用。

public void run() {synchronized(obj){for(int j = 0;j < 1000;j++){i++;}}
}

上面代碼中將 obj 作為鎖對象對其加鎖,每次當線程進入 synchronized 修飾的代碼塊時就會要求當前線程持有obj 實例對象鎖,如果當前有其他線程正持有該對象鎖,那么新到的線程就必須等待。

synchronized 修飾的代碼塊,除了可以鎖定對象之外,也可以對當前實例對象鎖、class 對象鎖進行鎖定

// 實例對象鎖
synchronized(this){for(int j = 0;j < 1000;j++){i++;}
}//class對象鎖
synchronized(TSynchronized.class){for(int j = 0;j < 1000;j++){i++;}
}

synchronized 底層原理

在簡單介紹完 synchronized 之后,我們就來聊一下 synchronized 的底層原理了。

我們或許都有所了解(下文會細致分析),synchronized 的代碼塊是由一組 monitorenter/monitorexit 指令實現的。而Monitor 對象是實現同步的基本單元。

啥是 Monitor 對象呢?

Monitor 對象

任何對象都關聯了一個管程,管程就是控制對象并發訪問的一種機制管程 是一種同步原語,在 Java 中指的就是 synchronized,可以理解為 synchronized 就是 Java 中對管程的實現。

管程提供了一種排他訪問機制,這種機制也就是 互斥。互斥保證了在每個時間點上,最多只有一個線程會執行同步方法。

所以你理解了 Monitor 對象其實就是使用管程控制同步訪問的一種對象。

對象內存布局

hotspot 虛擬機中,對象在內存中的布局分為三塊區域:

  • 對象頭(Header)
  • 實例數據(Instance Data)
  • 對齊填充(Padding)

這三塊區域的內存分布如下圖所示

image-20201202064002278

我們來詳細介紹一下上面對象中的內容。

對象頭 Header

對象頭 Header 主要包含 MarkWord 和對象指針 Klass Pointer,如果是數組的話,還要包含數組的長度。

img

在 32 位的虛擬機中 MarkWord ,Klass Pointer 和數組長度分別占用 32 位,也就是 4 字節。

如果是 64 位虛擬機的話,MarkWord ,Klass Pointer 和數組長度分別占用 64 位,也就是 8 字節。

在 32 位虛擬機和 64 位虛擬機的 Mark Word 所占用的字節大小不一樣,32 位虛擬機的 Mark Word 和 Klass Pointer 分別占用 32 bits 的字節,而 64 位虛擬機的 Mark Word 和 Klass Pointer 占用了64 bits 的字節,下面我們以 32 位虛擬機為例,來看一下其 Mark Word 的字節具體是如何分配的。

img

img

用中文翻譯過來就是

img

  • 無狀態也就是無鎖的時候,對象頭開辟 25 bit 的空間用來存儲對象的 hashcode ,4 bit 用于存放分代年齡,1 bit 用來存放是否偏向鎖的標識位,2 bit 用來存放鎖標識位為 01。
  • 偏向鎖 中劃分更細,還是開辟 25 bit 的空間,其中 23 bit 用來存放線程ID,2bit 用來存放 epoch,4bit 存放分代年齡,1 bit 存放是否偏向鎖標識, 0 表示無鎖,1 表示偏向鎖,鎖的標識位還是 01。
  • 輕量級鎖中直接開辟 30 bit 的空間存放指向棧中鎖記錄的指針,2bit 存放鎖的標志位,其標志位為 00。
  • 重量級鎖中和輕量級鎖一樣,30 bit 的空間用來存放指向重量級鎖的指針,2 bit 存放鎖的標識位,為 11
  • GC標記開辟 30 bit 的內存空間卻沒有占用,2 bit 空間存放鎖標志位為 11。

其中無鎖和偏向鎖的鎖標志位都是 01,只是在前面的 1 bit 區分了這是無鎖狀態還是偏向鎖狀態。

關于為什么這么分配的內存,我們可以從 OpenJDK 中的markOop.hpp類中的枚舉窺出端倪

img

來解釋一下

  • age_bits 就是我們說的分代回收的標識,占用4字節
  • lock_bits 是鎖的標志位,占用2個字節
  • biased_lock_bits 是是否偏向鎖的標識,占用1個字節。
  • max_hash_bits 是針對無鎖計算的 hashcode 占用字節數量,如果是 32 位虛擬機,就是 32 - 4 - 2 -1 = 25 byte,如果是 64 位虛擬機,64 - 4 - 2 - 1 = 57 byte,但是會有 25 字節未使用,所以 64 位的 hashcode 占用 31 byte。
  • hash_bits 是針對 64 位虛擬機來說,如果最大字節數大于 31,則取 31,否則取真實的字節數
  • cms_bits 我覺得應該是不是 64 位虛擬機就占用 0 byte,是 64 位就占用 1byte
  • epoch_bits 就是 epoch 所占用的字節大小,2 字節。

在上面的虛擬機對象頭分配表中,我們可以看到有幾種鎖的狀態:無鎖(無狀態),偏向鎖,輕量級鎖,重量級鎖,其中輕量級鎖和偏向鎖是 JDK1.6 中對 synchronized 鎖進行優化后新增加的,其目的就是為了大大優化鎖的性能,所以在 JDK 1.6 中,使用 synchronized 的開銷也沒那么大了。其實從鎖有無鎖定來講,還是只有無鎖和重量級鎖,偏向鎖和輕量級鎖的出現就是增加了鎖的獲取性能而已,并沒有出現新的鎖。

所以我們的重點放在對 synchronized 重量級鎖的研究上,當 monitor 被某個線程持有后,它就會處于鎖定狀態。在 HotSpot 虛擬機中,monitor 的底層代碼是由 ObjectMonitor 實現的,其主要數據結構如下(位于 HotSpot 虛擬機源碼 ObjectMonitor.hpp 文件,C++ 實現的)

這段 C++ 中需要注意幾個屬性:_WaitSet 、 _EntryList 和 _Owner,每個等待獲取鎖的線程都會被封裝稱為 ObjectWaiter 對象。

_Owner 是指向了 ObjectMonitor 對象的線程,而 _WaitSet 和 _EntryList 就是用來保存每個線程的列表。

那么這兩個列表有什么區別呢?這個問題我和你聊一下鎖的獲取流程你就清楚了。

鎖的兩個列表

當多個線程同時訪問某段同步代碼時,首先會進入 _EntryList 集合,當線程獲取到對象的 monitor 之后,就會進入 _Owner 區域,并把 ObjectMonitor 對象的 _Owner 指向為當前線程,并使 _count + 1,如果調用了釋放鎖(比如 wait)的操作,就會釋放當前持有的 monitor ,owner = null, _count - 1,同時這個線程會進入到 _WaitSet 列表中等待被喚醒。如果當前線程執行完畢后也會釋放 monitor 鎖,只不過此時不會進入 _WaitSet 列表了,而是直接復位 _count 的值。

Klass Pointer 表示的是類型指針,也就是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

你可能不是很理解指針是個什么概念,你可以簡單理解為指針就是指向某個數據的地址。

實例數據 Instance Data

實例數據部分是對象真正存儲的有效信息,也是代碼中定義的各個字段的字節大小,比如一個 byte 占 1 個字節,一個 int 占用 4 個字節。

對齊 Padding

對齊不是必須存在的,它只起到了**占位符(%d, %c 等)**的作用。這就是 JVM 的要求了,因為 HotSpot JVM 要求對象的起始地址必須是 8 字節的整數倍,也就是說對象的字節大小是 8 的整數倍,不夠的需要使用 Padding 補全。

鎖的升級流程

先來個大體的流程圖來感受一下這個過程,然后下面我們再分開來說

img

無鎖

無鎖狀態,無鎖即沒有對資源進行鎖定,所有的線程都可以對同一個資源進行訪問,但是只有一個線程能夠成功修改資源。

img

無鎖的特點就是在循環內進行修改操作,線程會不斷的嘗試修改共享資源,直到能夠成功修改資源并退出,在此過程中沒有出現沖突的發生,這很像我們在之前文章中介紹的 CAS 實現,CAS 的原理和應用就是無鎖的實現。無鎖無法全面代替有鎖,但無鎖在某些場合下的性能是非常高的。

偏向鎖

HotSpot 的作者經過研究發現,大多數情況下,鎖不僅不存在多線程競爭,還存在鎖由同一線程多次獲得的情況,偏向鎖就是在這種情況下出現的,它的出現是為了解決只有在一個線程執行同步時提高性能。

img

可以從對象頭的分配中看到,偏向鎖要比無鎖多了線程IDepoch,下面我們就來描述一下偏向鎖的獲取過程

偏向鎖獲取過程

  1. 首先線程訪問同步代碼塊,會通過檢查對象頭 Mark Word 的鎖標志位判斷目前鎖的狀態,如果是 01,說明就是無鎖或者偏向鎖,然后再根據是否偏向鎖 的標示判斷是無鎖還是偏向鎖,如果是無鎖情況下,執行下一步
  2. 線程使用 CAS 操作來嘗試對對象加鎖,如果使用 CAS 替換 ThreadID 成功,就說明是第一次上鎖,那么當前線程就會獲得對象的偏向鎖,此時會在對象頭的 Mark Word 中記錄當前線程 ID 和獲取鎖的時間 epoch 等信息,然后執行同步代碼塊。

全局安全點(Safe Point):全局安全點的理解會涉及到 C 語言底層的一些知識,這里簡單理解 SafePoint 是 Java 代碼中的一個線程可能暫停執行的位置。

等到下一次線程在進入和退出同步代碼塊時就不需要進行 CAS 操作進行加鎖和解鎖,只需要簡單判斷一下對象頭的 Mark Word 中是否存儲著指向當前線程的線程ID,判斷的標志當然是根據鎖的標志位來判斷的。如果用流程圖來表示的話就是下面這樣

在這里插入圖片描述

關閉偏向鎖

偏向鎖在Java 6 和Java 7 里是默認啟用的。由于偏向鎖是為了在只有一個線程執行同步塊時提高性能,如果你確定應用程序里所有的鎖通常情況下處于競爭狀態,可以通過JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認會進入輕量級鎖狀態。

關于 epoch

偏向鎖的對象頭中有一個被稱為 epoch 的值,它作為偏差有效性的時間戳。

輕量級鎖

輕量級鎖是指當前鎖是偏向鎖的時候,資源被另外的線程所訪問,那么偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,從而提高性能,下面是詳細的獲取過程。

輕量級鎖加鎖過程

  1. 緊接著上一步,如果 CAS 操作替換 ThreadID 沒有獲取成功,執行下一步
  2. 如果使用 CAS 操作替換 ThreadID 失敗(這時候就切換到另外一個線程的角度)說明該資源已被同步訪問過,這時候就會執行鎖的撤銷操作,撤銷偏向鎖,然后等原持有偏向鎖的線程到達全局安全點(SafePoint)時,會暫停原持有偏向鎖的線程,然后會檢查原持有偏向鎖的狀態,如果已經退出同步,就會喚醒持有偏向鎖的線程,執行下一步
  3. 檢查對象頭中的 Mark Word 記錄的是否是當前線程 ID,如果是,執行同步代碼,如果不是,執行偏向鎖獲取流程 的第2步。

如果用流程表示的話就是下面這樣(已經包含偏向鎖的獲取)

img

重量級鎖

重量級鎖其實就是 synchronized 最終加鎖的過程,在 JDK 1.6 之前,就是由無鎖 -> 加鎖的這個過程。

重量級鎖的獲取流程

  1. 接著上面偏向鎖的獲取過程,由偏向鎖升級為輕量級鎖,執行下一步
  2. 會在原持有偏向鎖的線程的棧中分配鎖記錄,將對象頭中的 Mark Word 拷貝到原持有偏向鎖線程的記錄中,然后原持有偏向鎖的線程獲得輕量級鎖,然后喚醒原持有偏向鎖的線程,從安全點處繼續執行,執行完畢后,執行下一步,當前線程執行第 4 步
  3. 執行完畢后,開始輕量級解鎖操作,解鎖需要判斷兩個條件
    • 判斷對象頭中的 Mark Word 中鎖記錄指針是否指向當前棧中記錄的指針

img

  • 拷貝在當前線程鎖記錄的 Mark Word 信息是否與對象頭中的 Mark Word 一致。

如果上面兩個判斷條件都符合的話,就進行鎖釋放,如果其中一個條件不符合,就會釋放鎖,并喚起等待的線程,進行新一輪的鎖競爭。

  1. 在當前線程的棧中分配鎖記錄,拷貝對象頭中的 MarkWord 到當前線程的鎖記錄中,執行 CAS 加鎖操作,會把對象頭 Mark Word 中鎖記錄指針指向當前線程鎖記錄,如果成功,獲取輕量級鎖,執行同步代碼,然后執行第3步,如果不成功,執行下一步
  2. 當前線程沒有使用 CAS 成功獲取鎖,就會自旋一會兒,再次嘗試獲取,如果在多次自旋到達上限后還沒有獲取到鎖,那么輕量級鎖就會升級為 重量級鎖

img

如果用流程圖表示是這樣的

img

根據上面對于鎖升級細致的描述,我們可以總結一下不同鎖的適用范圍和場景。

鎖類型適用場景缺點優點
偏向鎖適用于只有一個線程訪問的同步場景如果存在多個線程競爭使用鎖,會帶來額外的鎖撤銷消耗加鎖和解消耗小
輕量級鎖適用于追求響應時間的應用場景如果始終得不到資源,會自旋消耗 CPU提高程序響應速度
重量級鎖適用于追求吞吐量的應用場景得不到鎖的線程會阻塞,性能比較差阻塞,不需要消耗 CPU

synchronized 代碼塊的底層實現

為了便于方便研究,我們把 synchronized 修飾代碼塊的示例簡單化,如下代碼所示

public class SynchronizedTest {private int i;public void syncTask(){synchronized (this){i++;}}}

我們主要關注一下 synchronized 的字節碼,如下所示

在這里插入圖片描述

從這段字節碼中我們可以知道,同步語句塊使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令指向同步代碼塊的結束位置。

那么為什么會有兩個 monitorexit 呢?

不知道你注意到下面的異常表了嗎?如果你不知道什么是異常表,那么我建議你讀一下這篇文章

synchronized 修飾方法的底層原理

方法的同步是隱式的,也就是說 synchronized 修飾方法的底層無需使用字節碼來控制,真的是這樣嗎?我們來反編譯一波看看結果

public class SynchronizedTest {private int i;public synchronized void syncTask(){i++;}
}

這次我們使用 javap -verbose 來輸出詳細的結果

在這里插入圖片描述

從字節碼上可以看出,synchronized 修飾的方法并沒有使用 monitorenter 和 monitorexit 指令,取得代之是ACC_SYNCHRONIZED 標識,該標識指明了此方法是一個同步方法,JVM 通過該 ACC_SYNCHRONIZED 訪問標志來辨別一個方法是否聲明為同步方法,從而執行相應的同步調用。這就是 synchronized 鎖在同步代碼塊上和同步方法上的實現差別。

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

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

相關文章

華三(H3C)與華為(Huawei)設備配置IPsec VPN的詳細說明,涵蓋配置流程、參數設置及常見問題處理

以下是針對華三&#xff08;H3C&#xff09;與華為&#xff08;Huawei&#xff09;設備配置IPsec VPN的詳細說明&#xff0c;涵蓋配置流程、參數設置及常見問題處理&#xff1a; 一、華三&#xff08;H3C&#xff09;設備IPsec VPN配置詳解 1. 配置流程 華三IPsec VPN配置主要…

KBEngine 源代碼分析(一):pyscript 目錄文件介紹

pyscript 目錄文件 pyscript 目錄提供了 KBEngine 把 C++ 代碼中的類注冊到 Python 的機制 同時也提供了 C++ 調用 Python 方法的例子 相對現在的 C++ 17/20 ,這個目錄的分裝相對不優雅 不過不影響學習如何使用 Python 官方庫提供的 API ,實現 C++ Python 混合編程 C++ …

線程入門3

synchronized修飾方法 synchronized可以修飾代碼塊(在線程入門2中有例子)&#xff0c;也可以修飾普通方法和靜態方法。 修飾普通方法 修飾普通方法簡化寫法&#xff1a; 修飾靜態方法 修飾靜態方法簡化寫法&#xff1a; 注意&#xff1a;利用synchronized上鎖&#xff0c;鎖的…

linux上Flexlm命令

FlexLM 是一種靈活的許可證管理系統&#xff0c;廣泛用于各種軟件產品中&#xff0c;如 Autodesk 的 AutoCAD 和 Autodesk 的其他產品。它允許軟件開發商控制軟件的使用和分發&#xff0c;同時提供靈活的許可證管理策略。在 Linux 系統中使用 FlexLM 通常涉及到幾個關鍵步驟&am…

【Java學習方法】終止循環的關鍵字

終止循環的關鍵字 一、break 作用&#xff1a;跳出最近的循環&#xff08;直接結束離break最近的那層循環&#xff09; 使用場景&#xff1a;一般搭配if條件判斷&#xff0c;如果滿足某個條件&#xff0c;就結束循環&#xff0c;&#xff08;場景&#xff1a;常見于暴力枚舉中…

【論文精讀】Reformer:高效Transformer如何突破長序列處理瓶頸?

目錄 一、引言&#xff1a;當Transformer遇到長序列瓶頸二、核心技術解析&#xff1a;從暴力計算到智能優化1. 局部敏感哈希注意力&#xff08;LSH Attention&#xff09;&#xff1a;用“聚類篩選”替代“全量計算”關鍵步驟&#xff1a;數學優化&#xff1a; 2. 可逆殘差網絡…

關于在Springboot中設置時間格式問題

目錄 1-設置全局時間格式1.Date類型的時間2.JDK8時間3.使Date類和JDK8時間類統統格式化時間 2-關于DateTimeFormat注解 1-設置全局時間格式 1.Date類型的時間 對于老項目來說&#xff0c;springboot中許多類使用的是Date類型的時間&#xff0c;沒有用到LocalDateTime等JDK8時…

面試篇:Java并發與多線程

基礎概念 什么是線程&#xff1f;線程和進程的區別是什么&#xff1f; 線程 是程序執行的最小單位&#xff0c;它是 CPU 調度和執行的基本單元。一個進程可以包含多個線程&#xff0c;這些線程共享進程的資源&#xff08;如內存&#xff09;&#xff0c;但每個線程有自己的棧…

【Qt/C++】QPrinter關于QInternal::Printer的解析

1. 問題分析 QInternal::Printer在Qt框架中并不是一個直接暴露給用戶的API。相反&#xff0c;它是一個枚舉值&#xff0c;用于標識QPaintDevice的類型。在Qt中&#xff0c;QPaintDevice是一個抽象類&#xff0c;用于任何可以進行繪制的設備&#xff0c;如窗口、圖像、打印機等…

uniapp返回上一頁接口數據更新了,頁面未更新

注意&#xff1a;不是組件套組件可以不使用setTimeout延時 返回上一頁一般會走onshow&#xff0c;但是接口更新了頁面未更新 onShow(() > {// 切換城市后重新調用數據if (areaId.value) {const timer setTimeout(async () > {timer && clearTimeout(timer);…

MCU開發學習記錄11 - ADC學習與實踐(HAL庫) - 單通道ADC采集、多通道ADC采集、定時器觸發連續ADC采集 - STM32CubeMX

名詞解釋&#xff1a; ADC&#xff1a; Analog-to-Digital SAR&#xff1a;Successive Approximation Register 本文將介紹ADC的概念、相關函數以及STM32CubeMX生成ADC的配置函數。針對于ADC實踐&#xff1a;單通道采集芯片內部溫度傳感器&#xff08;ADC1_ch16&#xff09;&a…

68元撬動未來:明遠智睿2351開發板重塑嵌入式開發生態

在嵌入式開發領域&#xff0c;價格與性能的矛盾始終存在&#xff1a;高端開發板功能強大但成本高昂&#xff0c;低價產品則往往受限于性能與擴展性。明遠智睿2351開發板以68元&#xff08;含稅&#xff09;的定價打破這一僵局&#xff0c;通過四核1.4G處理器、全功能Linux系統與…

關于ubuntu密碼正確但是無法登錄的情況

參考這個文章&#xff1a; https://blog.csdn.net/cuichongxin/article/details/117462494 檢查一下是不是用戶被lock了 輸入passwd -s username 如果用戶是L狀態&#xff0c;那么就是lock了。 使用 passwd -u username 解鎖 關于 .bashrc 不生效 有幾點&#xff1a; ~/.…

LeetCode-47. 全排列 II

1、題目描述&#xff1a; 給定一個可包含重復數字的序列 nums &#xff0c;按任意順序 返回所有不重復的全排列。 示例 1&#xff1a; 輸入&#xff1a;nums [1,1,2] 輸出&#xff1a; [[1,1,2],[1,2,1],[2,1,1]]示例 2&#xff1a; 輸入&#xff1a;nums [1,2,3] 輸出&am…

Python 設計模式:訪問者模式

1. 什么是訪問者模式&#xff1f; 訪問者模式是一種行為設計模式&#xff0c;它允許你在不改變對象結構的前提下&#xff0c;定義新的操作。通過將操作封裝在訪問者對象中&#xff0c;訪問者模式使得你可以在不修改元素類的情況下&#xff0c;向元素類添加新的功能。 訪問者模…

基于stm32的智能門鎖系統

標題:基于stm32的智能門鎖系統 內容:1.摘要 摘要&#xff1a;隨著科技的飛速發展&#xff0c;人們對家居安全的要求日益提高&#xff0c;智能門鎖系統應運而生。本研究的目的是設計并實現一個基于STM32的智能門鎖系統。采用STM32微控制器作為核心控制單元&#xff0c;結合指紋…

GitHub 常見高頻問題與解決方案(實用手冊)

目錄 1.Push 提示權限錯誤(Permission denied) 2.push 報錯:rejected non-fast-forward 3.忘記添加 .gitignore,上傳了無關文件 4. 撤銷最近一次 commit 5.clone 太慢或失敗 6.如何切換/創建分支 7.如何合并分支 8.如何刪除遠程分支 9.如何 Fork + PR(Pull Reque…

【MySQL數據庫入門到精通-04 DML操作】

一、DML DML英文全稱是Data Manipulation Language(數據操作語言)&#xff0c;用來對數據庫中表的數據記錄進行增、刪、改操作。 二、添加數據 1.給指定字段添加數據 代碼如下&#xff08;示例&#xff09;&#xff1a; insert into 表名 &#xff08;字段1&#xff0c;字…

2022 年 9 月青少年軟編等考 C 語言六級真題解析

目錄 T1. 棧的基本操作T2. stack or queue思路分析T3. 合影效果T4. 發型糟糕的一天思路分析T1. 棧的基本操作 題目鏈接:SOJ D1188 此題為 2022 年 6 月三級第二題僅有棧操作的版本,見 2022 年 6 月青少年軟編等考 C 語言三級真題解析中的 T2。 T2. stack or queue 題目鏈…

美創市場競爭力突出!《2025中國數據安全市場研究報告》發布

數據要素時代&#xff0c;數據已成國家戰略性資源&#xff0c;數據安全關乎國家安全&#xff01;數說安全發布的《2025中國數據安全市場研究報告》&#xff08;以下簡稱《報告》&#xff09;顯示&#xff0c;2024年數據安全市場逆勢增長&#xff0c;市場規模首次突破百億。《報…