了不起在前兩天的時候給大家講述了關(guān)于這個 Java 的公平鎖,非公平鎖,共享鎖,獨占鎖,樂觀鎖,悲觀鎖,遞歸鎖,讀寫鎖,今天我們就再來了解一下其他的鎖,比如,輕量級鎖,重量級鎖,偏向鎖,以及分段鎖。
Java的輕量級鎖(Lightweight Locking)是Java虛擬機(JVM)中的一種優(yōu)化機制,用于減少多線程競爭時的性能開銷。在多線程環(huán)境中,當多個線程嘗試同時訪問共享資源時,通常需要某種形式的同步以防止數(shù)據(jù)不一致。Java提供了多種同步機制,如synchronized關(guān)鍵字和ReentrantLock,但在高并發(fā)場景下,這些機制可能導(dǎo)致性能瓶頸。
輕量級鎖是JVM中的一種鎖策略,它在沒有多線程競爭的情況下提供了較低的開銷,同時在競爭變得激烈時能夠自動升級到更重量級的鎖。這種策略的目標是在不需要時避免昂貴的線程阻塞操作。
不過這種鎖并不是通過Java語言直接暴露給開發(fā)者的API,而是JVM在運行時根據(jù)需要自動應(yīng)用的。因此,我們不能直接通過Java代碼來實現(xiàn)一個輕量級鎖。
但是我們可以使用Java提供的synchronized關(guān)鍵字或java.util.concurrent.locks.Lock接口(及其實現(xiàn)類,如ReentrantLock)來創(chuàng)建同步代碼塊或方法,這些同步機制在底層可能會被JVM優(yōu)化為使用輕量級鎖。
示例代碼:
public class LightweightLockExample { private Object lock = new Object(); private int sharedData; public void incrementSharedData() { synchronized (lock) { sharedData++; } } public int getSharedData() { synchronized (lock) { return sharedData; } } public static void main(String[] args) { LightweightLockExample example = new LightweightLockExample(); // 使用多個線程來訪問共享數(shù)據(jù) for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { example.incrementSharedData(); } }).start(); } // 等待所有線程執(zhí)行完畢 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 輸出共享數(shù)據(jù)的最終值 System.out.println("Final shared data value: " + example.getSharedData()); } }
這個示例中的同步塊在JVM內(nèi)部可能會使用輕量級鎖(具體是否使用取決于JVM的實現(xiàn)和運行時環(huán)境)
在這個例子中,我們有一個sharedData變量,多個線程可能會同時訪問它。我們使用synchronized塊來確保每次只有一個線程能夠修改sharedData。在JVM內(nèi)部,這些synchronized塊可能會使用輕量級鎖來優(yōu)化同步性能。
請注意,這個例子只是為了演示如何使用synchronized關(guān)鍵字,并不能保證JVM一定會使用輕量級鎖。實際上,JVM可能會根據(jù)運行時的情況選擇使用偏向鎖、輕量級鎖或重量級鎖。
在Java中,重量級鎖(Heavyweight Locking)是相對于輕量級鎖而言的,它涉及到線程阻塞和操作系統(tǒng)級別的線程調(diào)度。當輕量級鎖或偏向鎖不足以解決線程間的競爭時,JVM會升級鎖為重量級鎖。
重量級鎖通常是通過操作系統(tǒng)提供的互斥原語(如互斥量、信號量等)來實現(xiàn)的。當一個線程嘗試獲取已經(jīng)被其他線程持有的重量級鎖時,它會被阻塞(即掛起),直到持有鎖的線程釋放該鎖。在阻塞期間,線程不會消耗CPU資源,但會導(dǎo)致上下文切換的開銷,因為操作系統(tǒng)需要保存和恢復(fù)線程的上下文信息。
在Java中,synchronized關(guān)鍵字和java.util.concurrent.locks.ReentrantLock都可以導(dǎo)致重量級鎖的使用,尤其是在高并發(fā)和激烈競爭的場景下。
我們來看看使用synchronized可能會涉及到重量級鎖的代碼:
public class HeavyweightLockExample { private final Object lock = new Object(); private int counter; public void increment() { synchronized (lock) { counter++; } } public int getCounter() { synchronized (lock) { return counter; } } public static void main(String[] args) { HeavyweightLockExample example = new HeavyweightLockExample(); // 創(chuàng)建多個線程同時訪問共享資源 for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 10000; j++) { example.increment(); } }).start(); } // 等待所有線程完成 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } // 輸出計數(shù)器的值 System.out.println("Final counter value: " + example.getCounter()); } }
在這個示例中,多個線程同時訪問counter變量,并使用synchronized塊來確保每次只有一個線程能夠修改它。如果線程間的競爭非常激烈,JVM可能會將synchronized塊內(nèi)部的鎖升級為重量級鎖。
我們說的是可能哈,畢竟內(nèi)部操作還是由 JVM 具體來操控的。
我們再來看看這個ReentrantLock來實現(xiàn):
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); private int counter; public void increment() { lock.lock(); // 獲取鎖 try { counter++; } finally { lock.unlock(); // 釋放鎖 } } public int getCounter() { return counter; } public static void main(String[] args) { // 類似上面的示例,創(chuàng)建線程并訪問共享資源 } }
在這個示例中,ReentrantLock被用來同步對counter變量的訪問。如果鎖競爭激烈,ReentrantLock內(nèi)部可能會使用重量級鎖。
需要注意的是,重量級鎖的使用會帶來較大的性能開銷,因此在設(shè)計并發(fā)系統(tǒng)時應(yīng)盡量通過減少鎖競爭、使用更細粒度的鎖、使用無鎖數(shù)據(jù)結(jié)構(gòu)等方式來避免重量級鎖的使用。
在Java中,偏向鎖(Biased Locking)是Java虛擬機(JVM)為了提高無競爭情況下的性能而引入的一種鎖優(yōu)化機制。它的基本思想是,如果一個線程獲得了鎖,那么鎖就進入偏向模式,此時Mark Word的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu),當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程只需要檢查Mark Word的鎖標記位為偏向鎖以及當前線程ID等于Mark Word的Thread ID即可,這樣就省去了大量有關(guān)鎖申請的操作。
他和輕量級鎖和重量級鎖一樣,并不是直接通過Java代碼來控制的,而是由JVM在運行時自動進行的。因此,你不能直接編寫Java代碼來顯式地使用偏向鎖。不過,你可以編寫一個使用synchronized關(guān)鍵字的簡單示例,JVM可能會自動將其優(yōu)化為使用偏向鎖(取決于JVM的實現(xiàn)和運行時的配置)。
示例代碼:
public class BiasedLockingExample { // 這個對象用作同步的鎖 private final Object lock = new Object(); // 共享資源 private int sharedData; // 使用synchronized關(guān)鍵字進行同步的方法 public synchronized void synchronizedMethod() { sharedData++; } // 使用對象鎖進行同步的方法 public void lockedMethod() { synchronized (lock) { sharedData += 2; } } public static void main(String[] args) throws InterruptedException { // 創(chuàng)建示例對象 BiasedLockingExample example = new BiasedLockingExample(); // 使用Lambda表達式和Stream API創(chuàng)建并啟動多個線程 IntStream.range(0, 10).forEach(i -> { new Thread(() -> { // 每個線程多次調(diào)用同步方法 for (int j = 0; j < 10000; j++) { example.synchronizedMethod(); example.lockedMethod(); } }).start(); }); // 讓主線程睡眠一段時間,等待其他線程執(zhí)行完畢 Thread.sleep(2000); // 輸出共享數(shù)據(jù)的最終值 System.out.println("Final sharedData value: " + example.sharedData); } }
在這個示例中,我們有一個BiasedLockingExample類,它有兩個同步方法:synchronizedMethod和lockedMethod。synchronizedMethod是一個實例同步方法,它隱式地使用this作為鎖對象。lockedMethod是一個使用顯式對象鎖的方法,它使用lock對象作為鎖。
當多個線程調(diào)用這些方法時,JVM可能會觀察到只有一個線程在反復(fù)獲取同一個鎖,并且沒有其他線程競爭該鎖。在這種情況下,JVM可能會將鎖偏向到這個線程,以減少獲取和釋放鎖的開銷。
然而,請注意以下幾點:
由于偏向鎖是透明的優(yōu)化,因此你不需要在代碼中做任何特殊的事情來利用它。只需編寫正常的同步代碼,讓JVM來決定是否應(yīng)用偏向鎖優(yōu)化。
在Java中,"分段鎖"并不是一個官方的術(shù)語,但它通常被用來描述一種并發(fā)控制策略,其中數(shù)據(jù)結(jié)構(gòu)或資源被分成多個段,并且每個段都有自己的鎖。這種策略的目的是提高并發(fā)性能,允許多個線程同時訪問不同的段,而不會相互阻塞。
而在 Java 里面的經(jīng)典例子則是ConcurrentHashMap,在早期的ConcurrentHashMap實現(xiàn)中,內(nèi)部采用了一個稱為Segment的類來表示哈希表的各個段,每個Segment對象都持有一個鎖。這種設(shè)計允許多個線程同時讀寫哈希表的不同部分,而不會產(chǎn)生鎖競爭,從而提高了并發(fā)性能。
然而,需要注意的是,從Java 8開始,ConcurrentHashMap的內(nèi)部實現(xiàn)發(fā)生了重大變化。它不再使用Segment,而是采用了一種基于CAS(Compare-and-Swap)操作和Node數(shù)組的新設(shè)計,以及紅黑樹來處理哈希沖突。這種新設(shè)計提供了更高的并發(fā)性和更好的性能。盡管如此,"分段鎖"這個概念仍然可以用來描述這種將數(shù)據(jù)結(jié)構(gòu)分成多個可獨立鎖定的部分的通用策略。
我們看一個分段鎖實現(xiàn)安全計數(shù)器的代碼:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SegmentedCounter { private final int size; private final Lock[] locks; private final int[] counters; public SegmentedCounter(int size) { this.size = size; this.locks = new Lock[size]; this.counters = new int[size]; for (int i = 0; i < size; i++) { locks[i] = new ReentrantLock(); } } public void increment(int index) { locks[index].lock(); try { counters[index]++; } finally { locks[index].unlock(); } } public int getValue(int index) { locks[index].lock(); try { return counters[index]; } finally { locks[index].unlock(); } } }
在這個例子中,SegmentedCounter類有一個counters數(shù)組和一個locks數(shù)組。每個計數(shù)器都有一個與之對應(yīng)的鎖,這使得線程可以獨立地更新不同的計數(shù)器,而不會相互干擾。當然,這個簡單的例子并沒有考慮一些高級的并發(fā)問題,比如鎖的粒度選擇、鎖爭用和公平性等問題。在實際應(yīng)用中,你可能需要根據(jù)具體的需求和性能目標來調(diào)整設(shè)計。
所以,你學會了么?
本文鏈接:http://www.www897cc.com/showinfo-26-70469-0.htmlJava的ConcurrentHashMap是使用的分段鎖?
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 字節(jié)碼增強技術(shù),不止有 Java Proxy、 Cglib 和 Javassist 還有 Byte Buddy
下一篇: 十個Python編程小技巧