synchronized是jvm層面的鎖,使用簡單但靈活性差;lock是api層面的鎖,更靈活但需手動釋放。1.synchronized優(yōu)點:簡單易用、jvm自動管理鎖、可重入性;缺點:靈活性差、非公平、無法中斷。2.lock優(yōu)點:靈活性高、可中斷、可定時、公平性、支持多個condition;缺點:需手動釋放鎖、使用復雜、簡單場景性能可能較差。選擇時,若只需簡單同步,優(yōu)先選synchronized;若需復雜機制,則用lock,但務(wù)必在finally中釋放鎖。底層上,synchronized基于monitor機制,通過monitorenter和monitorexit指令實現(xiàn);lock常用實現(xiàn)類有reentrantlock、reentrantreadwritelock、stampedlock。忘記釋放lock會導致死鎖,避免方式是在finally塊中unlock。為減少性能問題,應(yīng)縮小同步范圍、用讀寫鎖、并發(fā)集合、原子類并避免長時間持鎖。
synchronized和Lock,都是Java中用于解決多線程并發(fā)問題的利器。簡單來說,synchronized是JVM層面的,用起來方便,但靈活性稍遜;Lock是API層面的,更靈活,但也需要手動釋放鎖。
解決方案
要理解synchronized和Lock的優(yōu)缺點,得先明白它們分別是怎么工作的。
立即學習“Java免費學習筆記(深入)”;
synchronized,你可以把它想象成一把內(nèi)置在對象里的鎖。當你用synchronized修飾一個方法或者代碼塊時,實際上是讓線程在進入這段代碼前,必須先獲得這把鎖。這把鎖是互斥的,也就是說,同一時刻,只能有一個線程持有它。用完之后,JVM會自動釋放鎖。
Lock,則是Java并發(fā)包java.util.concurrent.locks提供的接口,它有很多實現(xiàn)類,比如ReentrantLock。使用Lock,你需要手動獲取鎖(lock()方法)和釋放鎖(unlock()方法)。
synchronized的優(yōu)點:
- 簡單易用: 只需要一個關(guān)鍵字,就能實現(xiàn)線程同步。
- JVM支持: JVM會負責鎖的獲取和釋放,無需手動操作,不容易出錯。
- 可重入性: 同一個線程可以多次獲取同一把鎖,避免死鎖。
synchronized的缺點:
- 靈活性差: 只能用于方法或者代碼塊的同步,無法實現(xiàn)更復雜的鎖機制。
- 非公平性: 線程獲取鎖的順序是不確定的,可能導致某些線程一直無法獲取到鎖(饑餓)。
- 無法中斷: 一旦線程嘗試獲取synchronized鎖,就必須等待鎖被釋放,無法中斷。
Lock的優(yōu)點:
- 靈活性高: 提供了更多的鎖機制,比如公平鎖、可中斷鎖、定時鎖等。
- 可中斷性: 允許線程在等待鎖的過程中被中斷,避免長時間阻塞。
- 可定時性: 可以設(shè)置獲取鎖的超時時間,避免無限等待。
- 公平性: 可以創(chuàng)建公平鎖,保證線程按照請求鎖的順序獲取鎖。
- 可以綁定多個Condition: 允許一個Lock對象綁定多個Condition對象,實現(xiàn)更復雜的線程協(xié)作。
Lock的缺點:
- 需要手動釋放鎖: 必須在finally塊中手動釋放鎖,否則可能導致死鎖。
- 使用復雜: 相對于synchronized,Lock的使用更加復雜,需要更多的代碼。
- 性能損耗: 雖然在某些場景下Lock的性能可能優(yōu)于synchronized,但在簡單的同步場景下,Lock的性能可能不如synchronized。
如何選擇synchronized和Lock?
選擇synchronized還是Lock,主要取決于你的具體需求。
- 如果只需要簡單的同步,并且對性能要求不高,那么synchronized是一個不錯的選擇。 它的簡單易用性可以讓你快速地實現(xiàn)線程同步。
- 如果需要更復雜的鎖機制,比如公平鎖、可中斷鎖、定時鎖等,或者需要更高的靈活性,那么Lock是更好的選擇。 但需要注意的是,使用Lock需要更加小心,確保在finally塊中釋放鎖,避免死鎖。
synchronized的底層原理是什么?
synchronized的底層原理,其實涉及到JVM的monitor機制。每個Java對象都有一個與之關(guān)聯(lián)的monitor。當線程執(zhí)行到synchronized修飾的代碼塊時,會嘗試獲取monitor的所有權(quán)。如果monitor沒有被其他線程占用,那么線程就可以成功獲取monitor,并執(zhí)行代碼塊。如果monitor已經(jīng)被其他線程占用,那么線程就會被阻塞,直到monitor被釋放。
在JVM層面,synchronized是通過monitorenter和monitorexit指令來實現(xiàn)的。monitorenter指令用于獲取monitor的所有權(quán),monitorexit指令用于釋放monitor的所有權(quán)。
Lock接口有哪些常用的實現(xiàn)類?
Lock接口有很多實現(xiàn)類,其中最常用的就是ReentrantLock。ReentrantLock是一個可重入的互斥鎖,它提供了與synchronized類似的功能,但更加靈活。
除了ReentrantLock,還有ReentrantReadWriteLock、StampedLock等實現(xiàn)類。ReentrantReadWriteLock實現(xiàn)了讀寫鎖,允許多個線程同時讀取共享資源,但只允許一個線程寫入共享資源。StampedLock是Java 8新增的鎖,它提供了樂觀讀鎖和悲觀讀鎖,可以提高讀多寫少場景下的性能。
使用Lock時,忘記釋放鎖會發(fā)生什么?
使用Lock時,最常見的錯誤就是忘記釋放鎖。如果忘記釋放鎖,那么其他線程就永遠無法獲取到鎖,從而導致死鎖。
為了避免這種情況,務(wù)必在finally塊中釋放鎖。即使代碼塊中拋出異常,finally塊中的代碼也會被執(zhí)行,從而保證鎖能夠被釋放。
例如:
Lock lock = new ReentrantLock(); try { lock.lock(); // 臨界區(qū)代碼 } finally { lock.unlock(); }
如何避免synchronized和Lock帶來的性能問題?
synchronized和Lock雖然可以解決多線程并發(fā)問題,但也會帶來一定的性能問題。為了避免這些性能問題,可以采取以下措施: