Java進階(鎖)——鎖的升級,synchronized與lock鎖區別

目錄

  • 引出
    • Java中鎖升級
    • synchronized與lock鎖區別
  • 緩存三兄弟:緩存擊穿、穿透、雪崩
    • 緩存擊穿
    • 緩存穿透
    • 緩存雪崩
  • 總結

引出

Java進階(鎖)——鎖的升級,synchronized與lock鎖區別


Java中鎖升級

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

看一段代碼:

public class App {public static void main(String[] args) throws InterruptedException {Calculate cal = new Calculate();long start = System.currentTimeMillis();Thread t1 = new Thread(()->{for (int i = 0; i < 1000_0000; i++) {cal.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 1000_0000; i++) {cal.increase();}});t1.start();t2.start();t1.join();t2.join();System.out.println("time = " + (System.currentTimeMillis()-start));System.out.println("cal.getNum() = " + cal.getNum());}
}
public class Calculate {private int num;public int getNum() {return num;}// 多線程執行:public void increase() 會有線程安全問題// synchronized 鎖解決,鎖的是什么?public void increase(){synchronized (this) {num++;}}
}

分析:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

對象組成:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

Mark Word用于存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等等,占用內存大小與虛擬機位長一致。Mark Word對應的類型是markOop。源碼位于markOop.hpp中。在64位虛擬機下,Mark Word是64bit大小的,其存儲結構如下:

對象頭在64位虛擬機 8個字節

在這里插入圖片描述

在這里插入圖片描述

package cn.wn.juc;
import org.openjdk.jol.info.ClassLayout;
import java.util.concurrent.TimeUnit;
public class App {// 鎖升級演示public static void main(String[] args) throws InterruptedException {User user01 = new User();System.out.println("無狀態(001):" + ClassLayout.parseInstance(user01).toPrintable());// 從jdk6開始,jvm默認延遲4s自動開啟開啟偏向鎖。通過-XX:BiasedLockingStartupDelay=0設置取消延遲// 如果不要偏向鎖:-XX:-UseBiasedLocking=falseTimeUnit.SECONDS.sleep(5);User user02 = new User();System.out.println("啟用偏向鎖(101):" + ClassLayout.parseInstance(user02).toPrintable());for (int i = 0; i < 2; i++) {synchronized (user02) {System.out.println("偏向鎖(101)帶線程ID:" + ClassLayout.parseInstance(user02).toPrintable());}// 偏向鎖釋放,對象頭不會變化,一直存在, (偏向線程id) 下次執行判斷是否同一個線程如果是直接執行System.out.println("偏向鎖(101)釋放線程ID:" + ClassLayout.parseInstance(user02).toPrintable());}// 多個線程加鎖,升級為輕量級鎖new Thread(() -> {synchronized (user02) {System.out.println("輕量級鎖(00):" + ClassLayout.parseInstance(user02).toPrintable());try {System.out.println("=====休眠3秒======");TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("輕量-->重量(10):" + ClassLayout.parseInstance(user02).toPrintable());}}).start();TimeUnit.SECONDS.sleep(1);new Thread(() -> {synchronized (user02) {System.out.println("重量級鎖(10):" + ClassLayout.parseInstance(user02).toPrintable());}}).start();}
}
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.10</version>
</dependency>

鎖的狀態總共有四種,級別由低到高依次為:無鎖、偏向鎖、輕量級鎖、重量級鎖,這四種鎖狀態分別代表什么,為什么會有鎖升級?其實在 JDK 1.6之前,synchronized 還是一個重量級鎖,是一個效率比較低下的鎖,但是在JDK 1.6后,Jvm為了提高鎖的獲取與釋放效率對(synchronized )進行了優化,引入了 偏向鎖 和 輕量級鎖 ,從此以后鎖的狀態就有了四種(無鎖、偏向鎖、輕量級鎖、重量級鎖),并且四種狀態會隨著競爭的情況逐漸升級,而且是不可逆的過程,即不可降級,也就是說只能進行鎖升級(從低級別到高級別),不能鎖降級(高級別到低級別),意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。

無鎖

無鎖是指沒有對資源進行鎖定,所有的線程都能訪問并修改同一個資源,但同時只有一個線程能修改成功。

無鎖的特點是修改操作會在循環內進行,線程會不斷的嘗試修改共享資源。如果沒有沖突就修改成功并退出,否則就會繼續循環嘗試。如果有多個線程修改同一個值,必定會有一個線程能修改成功,而其他修改失敗的線程會不斷重試直到修改成功。

偏向鎖

初次執行到synchronized代碼塊的時候,鎖對象變成偏向鎖(通過CAS修改對象頭里的鎖標志位),字面意思是“偏向于第一個獲得它的線程”的鎖。執行完同步代碼塊后,線程并不會主動釋放偏向鎖。當第二次到達同步代碼塊時,線程會判斷此時持有鎖的線程是否就是自己(持有鎖的線程ID也在對象頭里),如果是則正常往下執行。由于之前沒有釋放鎖,這里也就不需要重新加鎖。如果自始至終使用鎖的線程只有一個,很明顯偏向鎖幾乎沒有額外開銷,性能極高。

偏向鎖是指當一段同步代碼一直被同一個線程所訪問時,即不存在多個線程的競爭時,那么該線程在后續訪問時便會自動獲得鎖,從而降低獲取鎖帶來的消耗,即提高性能。

當一個線程訪問同步代碼塊并獲取鎖時,會在 Mark Word 里存儲鎖偏向的線程 ID。在線程進入和退出同步塊時不再通過 CAS 操作來加鎖和解鎖,而是檢測 Mark Word 里是否存儲著指向當前線程的偏向鎖。輕量級鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時候依賴一次 CAS 原子指令即可。

偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程是不會主動釋放偏向鎖的。

輕量級鎖

輕量級鎖是指當鎖是偏向鎖的時候,卻被另外的線程所訪問,此時偏向鎖就會升級為輕量級鎖,其他線程會通過自旋(關于自旋的介紹見文末)的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。

一旦有第二個線程加入鎖競爭,偏向鎖就升級為輕量級鎖(自旋鎖)。這里要明確一下什么是鎖競爭:如果多個線程輪流獲取一個鎖,但是每次獲取鎖的時候都很順利,沒有發生阻塞,那么就不存在鎖競爭。只有當某線程嘗試獲取鎖的時候,發現該鎖已經被占用,只能等待其釋放,這才發生了鎖競爭。

在輕量級鎖狀態下繼續鎖競爭,沒有搶到鎖的線程將自旋,即不停地循環判斷鎖是否能夠被成功獲取。獲取鎖的操作,其實就是通過CAS修改對象頭里的鎖標志位。先比較當前鎖標志位是否為“釋放”,如果是則將其設置為“鎖定”,比較并設置是原子性發生的。這就算搶到鎖了,然后線程將當前鎖的持有者信息修改為自己。

長時間的自旋操作是非常消耗資源的,一個線程持有鎖,其他線程就只能在原地空耗CPU,執行不了任何有效的任務,這種現象叫做忙等(busy-waiting)。如果多個線程用一個鎖,但是沒有發生鎖競爭,或者發生了很輕微的鎖競爭,那么synchronized就用輕量級鎖,允許短時間的忙等現象。這是一種折衷的想法,短時間的忙等,換取線程在用戶態和內核態之間切換的開銷。

重量級鎖

重量級鎖顯然,此忙等是有限度的(有個計數器記錄自旋次數,默認允許循環10次,可以通過虛擬機參數更改)。如果鎖競爭情況嚴重,某個達到最大自旋次數的線程,會將輕量級鎖升級為重量級鎖(依然是CAS修改鎖標志位,但不修改持有鎖的線程ID)。當后續線程嘗試獲取鎖時,發現被占用的鎖是重量級鎖,則直接將自己掛起(而不是忙等),等待將來被喚醒。

重量級鎖是指當有一個線程獲取鎖之后,其余所有等待獲取該鎖的線程都會處于阻塞狀態。

簡言之,就是所有的控制權都交給了操作系統,由操作系統來負責線程間的調度和線程的狀態變更。而這樣會出現頻繁地對線程運行狀態的切換,線程的掛起和喚醒,從而消耗大量的系統資

synchronized與lock鎖區別

總結如下:

1?? lock 是一個接口,而 synchronized 是 Java 的一個關鍵字,synchronized 是內置的語言實現。
2?? 異常是否釋放鎖:synchronized 在發生異常時候會自動釋放占有的鎖,因此不會出現死鎖;而 lock 發生異常時候,不會主動釋放占有的鎖,必須手動 unlock 來釋放鎖,可能引起死鎖的發生。(所以最好將同步代碼塊用 try catch 包起來,finally 中寫入 unlock,避免死鎖的發生。)
3?? 是否響應中斷
lock 等待鎖過程中可以用 interrupt 來中斷等待,而 synchronized 只能等待鎖的釋放,不能響應中斷。
4?? 是否知道獲取鎖:Lock 可以通過 trylock 來知道有沒有獲取鎖,而 synchronized 不能。
5?? Lock 可以提高多個線程進行讀操作的效率。(可以通過 ReadWriteLock 實現讀寫分離)
6?? 在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時 Lock 的性能要遠遠優于 synchronized。所以說,在具體使用時要根據適當情況選擇。
7?? synchronized 使用 Object 對象本身的 wait 、notify、notifyAll 調度機制,而 Lock 可以使用 Condition 進行線程之間的調度。

性能區別

synchronized 和 lock 性能區別
synchronized 是托管給 JVM 執行的,而 lock 是 Java 寫的控制鎖的代碼。在 Java1.5 中,synchronized 是性能低效的。因為這是一個重量級操作,但是到了 Java1.6,發生了變化,進行了很多優化,有適應自旋,輕量級鎖,偏向鎖等等。
synchronized 原始采用的是 CPU悲觀鎖機制,即線程獲得的是排他鎖。排他鎖意味著其他線程只能依靠阻塞來等待線程釋放鎖。
而 Lock 用的是樂觀鎖機制。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖實現的機制就是 CAS 操作(Compare and Swap)。進一步研究 ReentrantLock 的源碼,會發現其中比較重要的獲得鎖的一個方法是 compareAndSetState。這里其實就是調用的 CPU 提供的特殊指令。

用途區別

synchronized 和 lock 用途區別
synchronized 原語和 ReentrantLock 在一般情況下沒有什么區別,但是在非常復雜的同步應用中,請考慮使用 ReentrantLock ,特別是遇到下面兩種需求的時候。
1?? 某個線程在等待一個鎖的控制權的這段時間需要中斷。
2?? 分開處理一些 wait-notify,ReentrantLock 里面的 Condition 應用,能夠控制 notify 哪個線程。
3?? 公平鎖功能,每個到來的線程都將排隊等候。

先說第一種情況,ReentrantLock 的 lock 機制有 2 種,忽略中斷鎖和響應中斷鎖,這帶來了很大的靈活性。比如:如果 A、B 兩個線程去競爭鎖,A 線程得到了鎖,B 線程等待,但是 A 線程這個時候實在有太多事情要處理,就是一直不返回,B 線程可能就會等不及了,想中斷自己,不再等待這個鎖了,轉而處理其他事情。

這個時候 ReentrantLock 就提供了兩種機制:可中斷/可不中斷:
①B 線程中斷自己(或者別的線程中斷它),但是 ReentrantLock 不去響應,繼續讓 B 線程等待,你再怎么中斷,我全當耳邊風(synchronized 原語就是如此);
②B 線程中斷自己(或者別的線程中斷它),ReentrantLock 處理了這個中斷,并且不再等待這個鎖的到來,完全放棄。

緩存三兄弟:緩存擊穿、穿透、雪崩

緩存擊穿

緩存擊穿:redis中沒有,但是數據庫有

順序:先查緩存,判斷緩存是否存在;如果緩存存在,直接返回數據;如果緩存不存在,則查詢數據庫,將數據庫的數據存入到緩存

在這里插入圖片描述

解決方案:將熱點數據設置過期時間長一點;針對數據庫的熱點訪問方法上分布式鎖;

緩存穿透

緩存穿透:redis中沒有,數據庫也沒有

在這里插入圖片描述

解決方案:

(1)將不存在的key,在redis設置值為null;

(2)使用布隆過濾器;

原理:https://zhuanlan.zhihu.com/p/616911933

在這里插入圖片描述

布隆過濾器:

如果確認key不存在于redis中,那么就一定不存在;

它說key存在,就有可能存在,也可能不存在! (誤差)

在這里插入圖片描述

布隆過濾器

1、根據配置類中的 key的數量 ,誤差率,計算位圖數組【二維數組】

2、通過布隆過濾器存放key的時候,會計算出需要多少個hash函數,由hash函數算出多少個位圖位置需要設定為1

3、查詢時,根據對應的hash函數,判斷對應的位置值是否都為1;如果有位置為0,則表示key一定不存在于該redis服務器中;如果全部位置都為1,則表示key可能存在于redis服務器中;

緩存雪崩

緩存雪崩:

Redis的緩存雪崩是指當Redis中大量緩存數據同時失效或者被清空時,大量的請求會直接打到數據庫上,導致數據庫瞬時壓力過大,甚至宕機的情況。

造成緩存雪崩的原因主要有兩個:

1.相同的過期時間:當Redis中大量的緩存數據設置相同的過期時間時,這些數據很可能會在同一時間點同時失效,導致大量請求直接打到數據庫上。

2.緩存集中失效:當服務器重啟、網絡故障等因素導致Redis服務不可用,且緩存數據沒有自動進行容錯處理,當服務恢復時大量的數據同時被重新加載到緩存中,也會導致大量請求直接打到數據庫上。

預防緩存雪崩的方法主要有以下幾種:

1.設置不同的過期時間:可以將緩存數據的過期時間分散開,避免大量緩存數據在同一時間點失效。

2.使用加鎖:可以將所有請求都先進行加鎖操作,當某個請求去查詢數據庫時,如果還沒有加載到緩存中,則只讓單個線程去執行加載操作,其他線程等待該線程完成后再次進行判斷,避免瞬間都去訪問數據庫從而引起雪崩。

3.提前加載預熱:在系統低峰期,可以提前將部分熱點數據加載到緩存中,這樣可以避免在高峰期緩存數據失效時全部打到數據庫上。

4.使用多級緩存:可以在Redis緩存之上再使用一層緩存,例如本地緩存等,當Redis緩存失效時,還能夠從本地緩存中獲取數據,避免直接打到數據庫上。

在這里插入圖片描述

本地緩存:ehcache oscache spring自帶緩存 持久層框架的緩存


總結

Java進階(鎖)——鎖的升級,synchronized與lock鎖區別

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

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

相關文章

Fastwhisper + Pyannote 實現 ASR + 說話者識別

文章目錄 前言一、faster-whisper簡單介紹二、pyannote.audio介紹三、faster-whisper pyannote.audio 實現語者識別四、多說幾句 前言 最近在研究ASR相關的業務&#xff0c;也是調研了不少模型&#xff0c;踩了不少坑&#xff0c;ASR這塊&#xff0c;目前中文普通話效果最好的…

Scrapy與分布式開發(1.1):課程導學

Scrapy與分布式開發&#xff1a;從入門到精通&#xff0c;打造高效爬蟲系統 課程大綱 在這個專欄中&#xff0c;我們將一起探索Scrapy框架的魅力&#xff0c;以及如何通過Scrapy-Redis實現分布式爬蟲的開發。在本課程導學中&#xff0c;我們將為您簡要介紹課程的學習目標、內容…

Verilog Coding Styles For Improved Simulation Efficiency論文學習記錄

原文基于Verilog-XL仿真器&#xff0c;測試了以下幾種方式對仿真效率的影響。 1. 使用 Case 語句而不是 if / else if 語句 八選一多路選擇器 case 實現效率比 if / else if 提升 6% 。 2. 如果可以盡量不使用 begin end 語句 使用 begin end 的 ff 觸發器比不使用 begin end …

初學者學習51還是STM32

初學者學習51還是STM32 在嵌入式系統領域&#xff0c;51和STM32是兩種常見的單片機架構。對于初學者來說&#xff0c;選擇學習哪種架構可能會成為一個難題。本文將對初學者學習51和STM32進行比較&#xff0c;以幫助讀者做出明智的選擇。 1. 51架構 51架構是指Intel 8051系列…

深度相機xyz點云文件三維坐標和jpg圖像文件二維坐標的相互變換函數

深度相機同時拍攝xyz點云文件和jpg圖像文件。xyz文件里面包含三維坐標[x,y,z]和jpg圖像文件包含二維坐標[x&#xff0c;y],但是不能直接進行變換&#xff0c;需要一定的步驟來推演。 下面函數是通過box二維框[xmin, ymin, xmax, ymax, _, _ ]去截取xyz文件中對應box里面的點云…

MyCAT學習——在openEuler22.03中安裝MyCAT2(網盤下載版)

準備工作 因為MyCAT 2基于JDK 1.8開發。也需要在虛擬機中安裝JDK&#xff08;JDK官網就能下載&#xff0c;我這提供一個捷徑&#xff09; jdk-8u401-linux-x64.rpmhttps://pan.baidu.com/s/1ywcDsxYOmfZONpmH9oDjfw?pwdrhel下載對應的tar安裝包,以及對應的jar包 安裝程序包…

九州金榜|孩子厭學要怎么辦?

孩子從小學到初中再到高中&#xff0c;孩子出現厭學情緒很正常&#xff0c;但是孩子出現厭學情緒后&#xff0c;就必然會影響到孩子學習成績&#xff0c;孩子產生厭學情緒的原因有哪些呢&#xff1f;只有找準孩子厭學原因才能去幫助孩子怎樣去克服孩子厭學情緒&#xff0c;下面…

ajax請求servlet成功但接收不到返回數據問題

ajax請求servlet成功但接收不到返回數據問題 javaweb初學者&#xff0c;最近老師布置的課設&#xff0c;所有功能都完成了&#xff0c;唯獨ajax與servlet交互出現問題&#xff0c;無論怎么調試都收不到數據 查詢兩天無果&#xff0c;剛才無意間看到 Crabime前輩的文章才恍然大…

深入解析YOLO:實時目標檢測技術的革命者

深入解析YOLO&#xff1a;實時目標檢測技術的革命者 目標檢測作為計算機視覺領域的一個核心任務&#xff0c;一直以來都是研究的熱點。而YOLO&#xff08;You Only Look Once&#xff09;技術作為其中的杰出代表&#xff0c;以其獨特的處理方式和卓越的性能&#xff0c;成為了…

day34貪心算法 part03

1005. K 次取反后最大化的數組和 簡單 給你一個整數數組 nums 和一個整數 k &#xff0c;按以下方法修改該數組&#xff1a; 選擇某個下標 i 并將 nums[i] 替換為 -nums[i] 。 重復這個過程恰好 k 次。可以多次選擇同一個下標 i 。 以這種方式修改數組后&#xff0c;返回數…

力扣棧隊列篇

以下思路來自代碼隨想錄以及官方題解。 文章目錄 232.用棧實現隊列225.用隊列實現棧20.有效的括號1047.刪除字符串中所有相鄰重復項150.逆波蘭表達式求值347.前K個高頻元素 232.用棧實現隊列 請你僅使用兩個棧實現先入先出隊列。隊列應當支持一般隊列支持的所有操作&#xff…

OSError: [WinError 1455] 頁面文件太小,無法完成操作。

[問題描述]&#xff1a;OSError: [WinError 1455] 頁面文件太小&#xff0c;無法完成操作。 原因1&#xff1a;線程數太大 方法&#xff1a;改小線程&#xff08;workers&#xff09;數。 原因2&#xff1a;虛擬內存太小或為0&#xff0c;調大虛擬內存。 方法&#xff1a;右鍵…

mysql索引過長Specialed key was too long的解決方法

在創建要給表的時候遇到一個有意思的問題&#xff0c;提示Specified key was too long; max key length is 767 bytes&#xff0c;從描述上來看&#xff0c;是Key太長&#xff0c;超過了指定的 767字節限制。通常出現在嘗試創建一個過長的唯一鍵&#xff08;UNIQUE KEY&#xf…

Vue.js 實用技巧:深入理解 Vue.mixin

&#x1f90d; 前端開發工程師、技術日更博主、已過CET6 &#x1f368; 阿珊和她的貓_CSDN博客專家、23年度博客之星前端領域TOP1 &#x1f560; 牛客高級專題作者、打造專欄《前端面試必備》 、《2024面試高頻手撕題》 &#x1f35a; 藍橋云課簽約作者、上架課程《Vue.js 和 E…

uniapp真機運行的時候顯示同步資源失敗,未得到同步資源的授權,請停止運行后重新運行,并注意手機上的授權提示

1、問題 在添加清單文件之前&#xff0c;項目運行都是好好的&#xff0c;添加了清單項目以后&#xff0c;基座一打就報這個錯&#xff0c;并且手機在安裝基座的時候會提示解析包時失敗&#xff0c; 2、解決方案 打開我的清單文件&#xff0c;我發現我和官網寫的清單文件不一…

華為OD機試“HJ2計算某字符出現次數”不區分大小寫Java編程解答

描述 寫出一個程序&#xff0c;接受一個由字母、數字和空格組成的字符串&#xff0c;和一個字符&#xff0c;然后輸出輸入字符串中該字符的出現次數。&#xff08;不區分大小寫字母&#xff09; 數據范圍&#xff1a; 1≤n≤1000 輸入描述&#xff1a; 第一行輸入一個由字…

【Linux進程間通信】共享內存

【Linux進程間通信】共享內存 目錄 【Linux進程間通信】共享內存system V共享內存共享內存示意圖共享內存的數據結構共享內存函數將共享內存掛接到對應的進程將共享內存取消掛接釋放共享內存 共享內存的特性共享內存擴展共享內存配合管道進行使用 作者&#xff1a;愛寫代碼的剛…

用docker部署后端項目

一、搭建局域網 1.1、介紹前后端項目搭建 需要4臺服務器&#xff0c;在同一個局域網中 1.2、操作 # 搭建net-ry局域網&#xff0c;用于部署若依項目 net-ry&#xff1a;名字 docker network create net-ry --subnet172.68.0.0/16 --gateway172.68.0.1#查看 docker network ls…

Git 安全遠程訪問:SSH 密鑰對生成、添加和連接步驟解析

使用 SSH 密鑰對的 Git 安全遠程訪問&#xff1a;生成、添加和連接 SSH&#xff08;Secure Shell&#xff09;是一種用于安全遠程訪問的協議&#xff0c;它提供了加密通信和身份驗證機制。在使用 SSH 連接到遠程 Git 存儲庫時&#xff0c;您可以使用 SSH 密鑰對來確保安全性。…

3d模型合并后一片漆黑是什么原因,怎么解決---模大獅模型網

當合并多個3D模型后&#xff0c;發現整個合并后的模型顯示為一片漆黑通常是由以下幾個可能的原因導致的&#xff1a; 材質設置問題&#xff1a;合并后的模型可能存在材質設置錯誤&#xff0c;導致模型無法正確顯示。檢查每個模型的材質屬性&#xff0c;確保其正確設置&#xff…