Java中的內(nèi)存模型(JMM)和鎖機(jī)制詳解
Java內(nèi)存模型(Java Memory Model, JMM)是Java虛擬機(jī)(JVM)規(guī)范中定義的一種內(nèi)存模型(抽象概念),它描述了Java程序中各種變量(包括實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問規(guī)則,以及在多線程環(huán)境下這些變量如何被線程共享和同步(變量的共享和同步機(jī)制)。
JMM的主要目標(biāo)是//定義線程如何以及何時(shí)可以看到由其他線程修改過的共享變量的值,以及在必須時(shí)進(jìn)行同步的機(jī)制和方式//,//既定義線程如何與主內(nèi)存進(jìn)行交互,以及如何保證原子性、可見性和有序性//。它幫助程序員編寫出在多線程環(huán)境下能夠正確運(yùn)行的Java程序。
Java內(nèi)存模型(JMM)
Java內(nèi)存模型定義了線程和主內(nèi)存之間的抽象關(guān)系,以及線程之間如何共享變量。它主要解決了多線程環(huán)境下,變量可見性和有序性的問題。
- 變量可見性:當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。在Java中,這通常通過volatile關(guān)鍵字或synchronized塊來(lái)保證。
- 有序性:JMM允許編譯器和處理器對(duì)指令進(jìn)行重排序以優(yōu)化性能,但這可能會(huì)導(dǎo)致多線程程序出現(xiàn)意外的行為。通過volatile關(guān)鍵字或synchronized塊可以禁止這種重排序。
JMM規(guī)定了所有變量都存儲(chǔ)在主內(nèi)存中,每個(gè)線程還有自己的工作內(nèi)存(線程棧的一部分),線程對(duì)變量的所有操作都必須在工作內(nèi)存中完成,然后再刷新到主內(nèi)存。
JMM定義的關(guān)鍵概念
- 主內(nèi)存:Java堆內(nèi)存中的方法區(qū)以及所有Java線程共享的內(nèi)存區(qū)域。所有變量都存儲(chǔ)在主內(nèi)存中,包括實(shí)例變量、靜態(tài)變量和數(shù)組元素,。
- 工作內(nèi)存:每個(gè)線程都有自己的工作內(nèi)存(也稱為本地內(nèi)存),它是線程私有的,包含了該線程對(duì)共享變量的私有副本以及線程執(zhí)行所需的其他信息(如指令、常量等)。線程對(duì)共享變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行(不能直接讀寫主內(nèi)存中的變量),然后再刷新回主內(nèi)存。
- 可見性:一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。JMM通過規(guī)定線程在何時(shí)必須將工作內(nèi)存中的變量值刷新回主內(nèi)存,以及何時(shí)必須從主內(nèi)存中重新讀取共享變量的值,來(lái)實(shí)現(xiàn)可見性。(大概就是:不同線程之間無(wú)法直接訪問對(duì)方工作內(nèi)存中的變量,線程間的變量值的傳遞均需要通過主內(nèi)存來(lái)完成,當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改)
- 原子性:一個(gè)或多個(gè)操作在執(zhí)行過程中不會(huì)被其他線程中斷。Java內(nèi)存模型只保證基本數(shù)據(jù)類型的讀取和賦值是原子的,對(duì)于復(fù)合操作(如i++),則需要通過同步機(jī)制來(lái)保證原子性。(大概就是:一個(gè)操作要么全部完成,要么完全不發(fā)生,不會(huì)被線程調(diào)度機(jī)制中斷)
- 有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。但是,在并發(fā)環(huán)境下,由于指令重排序(Instruction Reorder)的存在,程序的執(zhí)行順序可能與代碼順序不一致。Java內(nèi)存模型允許編譯器和處理器對(duì)指令進(jìn)行重排序,以提高程序執(zhí)行的性能。但是,重排序過程不會(huì)違反as-if-serial語(yǔ)義,即程序執(zhí)行的結(jié)果應(yīng)該與在單線程中按代碼順序執(zhí)行的結(jié)果一致。(大概就是:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行,但在多線程環(huán)境中,由于指令重排序等優(yōu)化手段,實(shí)際的執(zhí)行順序可能與代碼順序不一致)
Java提供的同步機(jī)制
為了保證多線程程序的正確性,Java提供了多種同步機(jī)制,如synchronized關(guān)鍵字、volatile關(guān)鍵字、Lock接口等,這些機(jī)制可以幫助程序員控制線程之間的內(nèi)存可見性和操作的有序性。
主要的同步機(jī)制
synchronized關(guān)鍵字
這是Java最基本的同步機(jī)制。它可以用來(lái)修飾方法或代碼塊,確保在同一時(shí)刻只有一個(gè)線程能夠執(zhí)行該方法或代碼塊。它通過鎖機(jī)制(通常是對(duì)象鎖或類鎖)來(lái)確保同一時(shí)刻只有一個(gè)線程能執(zhí)行某個(gè)方法或代碼塊。
修飾方法時(shí),它會(huì)自動(dòng)鎖定調(diào)用該方法的對(duì)象或該類(如果是靜態(tài)方法)。
修飾代碼塊時(shí),需要指定一個(gè)鎖對(duì)象,多個(gè)線程需要對(duì)該鎖對(duì)象進(jìn)行競(jìng)爭(zhēng),以獲得執(zhí)行權(quán)限。
使用synchronized關(guān)鍵字的示例
public class Counter { private int count = 0; // 使用synchronized修飾方法,確保線程安全 public synchronized void increment() { count++; // 這是一個(gè)非原子操作,但在synchronized方法中它是安全的 } public synchronized int getCount() { return count; } public static void main(String[] args) throws InterruptedException { final Counter counter = new Counter(); // 創(chuàng)建兩個(gè)線程來(lái)更新計(jì)數(shù)器 Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) { counter.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) { counter.increment(); } }); // 啟動(dòng)線程 t1.start(); t2.start(); // 等待線程完成 t1.join(); t2.join(); // 打印最終計(jì)數(shù)器值 System.out.println("Final count: " + counter.getCount()); // 應(yīng)該是20000 } }
volatile關(guān)鍵字
用于確保變量的可見性。當(dāng)一個(gè)變量被volatile修飾后,它會(huì)告訴JVM該變量的值可能會(huì)被其他線程修改,因此在每次讀取該變量時(shí)都需要重新從主內(nèi)存中讀取,而不是從線程的工作內(nèi)存中讀取。
注意,volatile不能確保操作的原子性,它僅適用于那些不需要同步代碼塊就能保證原子性的操作。
使用volatile關(guān)鍵字的示例
public class VolatileExample { // 使用volatile修飾共享變量,確保所有線程都能看到最新的值 private volatile boolean running = true; // 啟動(dòng)一個(gè)線程來(lái)模擬運(yùn)行中的任務(wù) public void startTask() { Thread taskThread = new Thread(() -> { while (running) { // 模擬任務(wù)執(zhí)行,這里只是簡(jiǎn)單地打印信息 System.out.println("Task is running..."); try { // 為了演示,讓線程暫停一段時(shí)間 Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 保持中斷狀態(tài) return; } } System.out.println("Task has stopped."); }); // 啟動(dòng)線程 taskThread.start(); // 假設(shè)在一段時(shí)間后,我們想要停止任務(wù) try { Thread.sleep(5000); // 等待5秒來(lái)模擬任務(wù)運(yùn)行時(shí)間 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } // 更新volatile變量以停止任務(wù) running = false; } public static void main(String[] args) { VolatileExample example = new VolatileExample(); example.startTask(); // 注意:在實(shí)際應(yīng)用中,你可能需要等待taskThread真正結(jié)束,這里為了簡(jiǎn)化示例而省略了 } }
在這個(gè)示例中,running變量被volatile修飾,這意味著當(dāng)running的值被修改時(shí),所有線程都會(huì)看到最新的值。在startTask方法中,我們啟動(dòng)了一個(gè)線程來(lái)模擬一個(gè)長(zhǎng)時(shí)間運(yùn)行的任務(wù),該任務(wù)通過檢查running變量的值來(lái)決定是否繼續(xù)執(zhí)行。在主線程中,我們通過將running設(shè)置為false來(lái)停止任務(wù)。由于running是volatile的,因此當(dāng)它被更新時(shí),運(yùn)行任務(wù)的線程能夠立即看到這一變化,并相應(yīng)地停止執(zhí)行。
wait()和notify()/notifyAll()方法
這三個(gè)方法都是Object類中的方法,用于線程間的通信。
wait()方法會(huì)使當(dāng)前線程等待,直到其他線程調(diào)用該對(duì)象的notify()或notifyAll()方法。調(diào)用wait()方法的線程必須持有該對(duì)象的鎖,調(diào)用后該線程會(huì)釋放鎖并進(jìn)入等待狀態(tài)。
notify()方法會(huì)喚醒等待該對(duì)象鎖的單個(gè)線程(如果有的話),而notifyAll()會(huì)喚醒所有等待該對(duì)象鎖的線程。
使用wait()和notify()/notifyAll()方法的示例
public class WaitNotifyExample { private final Object lock = new Object(); // 鎖對(duì)象 private boolean ready = false; // 條件變量 // 生產(chǎn)者線程調(diào)用此方法 public void produce() throws InterruptedException { synchronized (lock) { // 假設(shè)生產(chǎn)者需要做一些準(zhǔn)備工作 System.out.println("Producer is preparing..."); Thread.sleep(1000); // 模擬耗時(shí)操作 // 準(zhǔn)備工作完成,設(shè)置條件變量并通知等待的消費(fèi)者 ready = true; lock.notify(); // 喚醒等待在該鎖上的一個(gè)線程 System.out.println("Product is ready. Notified consumer."); } } // 消費(fèi)者線程調(diào)用此方法 public void consume() throws InterruptedException { synchronized (lock) { // 等待產(chǎn)品準(zhǔn)備好 while (!ready) { lock.wait(); // 釋放鎖并進(jìn)入等待狀態(tài) } // 產(chǎn)品已準(zhǔn)備好,開始消費(fèi) System.out.println("Consumer is consuming the product."); Thread.sleep(1000); // 模擬消費(fèi)過程 // 消費(fèi)完成,重置條件變量供下一輪使用 ready = false; } } public static void main(String[] args) { WaitNotifyExample example = new WaitNotifyExample(); // 創(chuàng)建生產(chǎn)者線程 Thread producer = new Thread(() -> { try { example.produce(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // 創(chuàng)建消費(fèi)者線程 Thread consumer = new Thread(() -> { try { example.consume(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); // 啟動(dòng)線程 producer.start(); consumer.start(); // 注意:在這個(gè)簡(jiǎn)單的示例中,沒有等待消費(fèi)者線程完成。 // 在實(shí)際應(yīng)用中,你可能需要某種形式的同步來(lái)確保生產(chǎn)者和消費(fèi)者之間的正確交互。 } }
請(qǐng)注意,在這個(gè)示例中,wait() 方法被放在了一個(gè) while 循環(huán)中。這是因?yàn)?wait() 方法可能會(huì)由于“虛假喚醒”(spurious wakeup)而被喚醒,即使沒有線程調(diào)用 notify() 或 notifyAll()。因此,將 wait() 放在 while 循環(huán)中并使用條件變量來(lái)檢查實(shí)際狀態(tài)是一種常見且推薦的做法。
此外,還需要注意,wait(), notify(), 和 notifyAll() 方法必須在同步代碼塊或同步方法中調(diào)用,因?yàn)樗鼈円蕾囉趯?duì)象鎖。在這個(gè)示例中,我們使用了一個(gè)單獨(dú)的鎖對(duì)象 lock 來(lái)控制對(duì)共享資源(這里是 ready 變量)的訪問和線程間的通信。
Lock接口
Java 5.0引入了java.util.concurrent.locks包,其中定義了Lock接口。Lock接口提供了比synchronized關(guān)鍵字更靈活的鎖定機(jī)制。
Lock接口的實(shí)現(xiàn)類(如ReentrantLock,它支持可重入的互斥鎖,還支持嘗試非阻塞地獲取鎖、可中斷地獲取鎖、定時(shí)嘗試獲取鎖等功能)提供了更加豐富的功能,如嘗試非阻塞地獲取鎖、可中斷地獲取鎖、定時(shí)嘗試獲取鎖等。
使用ReentrantLock的示例
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class CounterWithLock { private int count = 0; private final Lock lock = new ReentrantLock(); public void increment() { lock.lock(); // 獲取鎖 try { count++; } finally { lock.unlock(); // 釋放鎖,無(wú)論是否發(fā)生異常 } } public int getCount() { lock.lock(); // 這里也可以考慮使用tryLock()或讀寫鎖等更細(xì)粒度的控制 try { return count; } finally { lock.unlock(); } } // main方法和上面的示例類似,只是Counter類被替換為了CounterWithLock類 }
Condition接口
Condition接口是與Lock接口配合使用的,是Lock接口提供的一個(gè)條件對(duì)象,用于線程間的通信,它提供了更靈活的線程間通信方式。
每個(gè)Lock對(duì)象都可以創(chuàng)建多個(gè)Condition實(shí)例,用于實(shí)現(xiàn)更復(fù)雜的線程間通信。
使用Condition接口示例
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionExample { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; // 線程執(zhí)行的任務(wù) public void task() throws InterruptedException { lock.lock(); // 獲取鎖 try { // 等待條件滿足 while (!ready) { condition.await(); // 釋放鎖并進(jìn)入等待狀態(tài) } // 條件滿足,執(zhí)行任務(wù) System.out.println("Condition met, task is executing."); // 假設(shè)任務(wù)完成后需要重置條件 ready = false; } finally { lock.unlock(); // 無(wú)論如何,最后都要釋放鎖 } } // 觸發(fā)條件的方法 public void triggerCondition() { lock.lock(); // 獲取鎖 try { ready = true; // 設(shè)置條件為true condition.signalAll(); // 喚醒所有等待該條件的線程 } finally { lock.unlock(); // 釋放鎖 } } public static void main(String[] args) { ConditionExample example = new ConditionExample(); // 創(chuàng)建并啟動(dòng)線程 Thread thread = new Thread(() -> { try { example.task(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); thread.start(); // 主線程等待一會(huì)兒,然后觸發(fā)條件 try { Thread.sleep(1000); // 等待一秒,模擬其他操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } example.triggerCondition(); // 觸發(fā)條件,喚醒等待的線程 } }
在這個(gè)示例中,我們定義了一個(gè)ConditionExample類,它包含一個(gè)ReentrantLock鎖和一個(gè)Condition條件對(duì)象。task()方法模擬了一個(gè)線程需要等待某個(gè)條件滿足才能執(zhí)行的任務(wù),而triggerCondition()方法則用于在滿足某個(gè)條件時(shí)觸發(fā)該條件,從而喚醒等待的線程。
注意,在調(diào)用condition.await()時(shí),線程會(huì)釋放鎖并進(jìn)入等待狀態(tài),直到其他線程調(diào)用condition.signal()或condition.signalAll()來(lái)喚醒它。同樣地,在調(diào)用這些方法之前,必須持有與Condition對(duì)象相關(guān)聯(lián)的鎖。這是通過調(diào)用lock.lock()來(lái)完成的,并且在方法結(jié)束時(shí)通過lock.unlock()來(lái)釋放鎖,以確保鎖的釋放總是會(huì)發(fā)生,無(wú)論方法是否成功執(zhí)行。
Semaphore(信號(hào)量)
Semaphore是一個(gè)計(jì)數(shù)信號(hào)量,是一種基于計(jì)數(shù)的同步機(jī)制,可以用來(lái)控制同時(shí)訪問某個(gè)特定資源的線程數(shù)量。它不是直接通過某個(gè)接口或類的形式實(shí)現(xiàn)的,而是java.util.concurrent包中的一個(gè)類。
它允許多個(gè)線程同時(shí)訪問某個(gè)資源,但會(huì)限制同時(shí)訪問的線程數(shù)。
使用Semaphore(信號(hào)量)的示例
import java.util.concurrent.Semaphore; public class SemaphoreExample { // 創(chuàng)建一個(gè)Semaphore對(duì)象,設(shè)置許可數(shù)量為2 // 這意味著同時(shí)最多只能有兩個(gè)線程可以訪問資源 private final Semaphore semaphore = new Semaphore(2); // 模擬的資源訪問方法 public void accessResource() { try { // 請(qǐng)求許可 semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " has acquired a permit and is accessing the resource."); // 模擬資源訪問的耗時(shí)操作 Thread.sleep(1000); // 訪問完成后,釋放許可 semaphore.release(); System.out.println(Thread.currentThread().getName() + " has released the permit."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 保持中斷狀態(tài) System.out.println(Thread.currentThread().getName() + " was interrupted while accessing the resource."); } } public static void main(String[] args) { SemaphoreExample example = new SemaphoreExample(); // 創(chuàng)建并啟動(dòng)多個(gè)線程來(lái)訪問資源 for (int i = 0; i < 5; i++) { new Thread(() -> { example.accessResource(); }, "Thread-" + (i + 1)).start(); } } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)Semaphore對(duì)象,其初始許可數(shù)量為2。然后,我們定義了一個(gè)accessResource方法來(lái)模擬對(duì)資源的訪問。在訪問資源之前,線程會(huì)嘗試通過調(diào)用semaphore.acquire()來(lái)獲取許可。由于許可數(shù)量被限制為2,因此同時(shí)最多只有兩個(gè)線程能夠進(jìn)入accessResource方法的主體部分。一旦線程完成了對(duì)資源的訪問,它就會(huì)通過調(diào)用semaphore.release()來(lái)釋放許可,從而允許其他等待的線程獲取許可并訪問資源。
運(yùn)行這個(gè)程序,你會(huì)看到類似以下的輸出(具體順序可能會(huì)有所不同,因?yàn)榫€程的執(zhí)行是并發(fā)的):
Thread-1 has acquired a permit and is accessing the resource.
Thread-2 has acquired a permit and is accessing the resource.
Thread-1 has released the permit.
Thread-3 has acquired a permit and is accessing the resource.
Thread-2 has released the permit.
Thread-4 has acquired a permit and is accessing the resource.
Thread-3 has released the permit.
Thread-5 has acquired a permit and is accessing the resource.
Thread-4 has released the permit. Thread-5 has released the permit.
CountDownLatch(倒計(jì)時(shí)鎖存器)
CountDownLatch是一種同步輔助類,它允許一個(gè)或多個(gè)線程等待直到在其他線程中執(zhí)行的一組操作完成。它也不是通過接口或繼承關(guān)系實(shí)現(xiàn)的,而是直接作為一個(gè)類存在。
它通常用于等待直到一組異步操作完成。
使用CountDownLatch(倒計(jì)時(shí)鎖存器)的示例
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { // 創(chuàng)建一個(gè)CountDownLatch實(shí)例,設(shè)置計(jì)數(shù)器的初始值為2 // 這意味著我們需要等待兩個(gè)任務(wù)完成 CountDownLatch latch = new CountDownLatch(2); // 創(chuàng)建一個(gè)線程池來(lái)執(zhí)行任務(wù) ExecutorService executor = Executors.newFixedThreadPool(2); // 提交第一個(gè)任務(wù) executor.submit(() -> { try { // 模擬任務(wù)執(zhí)行 System.out.println("Task 1 is running"); Thread.sleep(1000); // 假設(shè)任務(wù)需要1秒來(lái)完成 System.out.println("Task 1 is done"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 任務(wù)完成后,計(jì)數(shù)器的值減1 latch.countDown(); } }); // 提交第二個(gè)任務(wù) executor.submit(() -> { try { // 模擬任務(wù)執(zhí)行 System.out.println("Task 2 is running"); Thread.sleep(2000); // 假設(shè)這個(gè)任務(wù)需要2秒來(lái)完成 System.out.println("Task 2 is done"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 任務(wù)完成后,計(jì)數(shù)器的值減1 latch.countDown(); } }); // 等待直到所有任務(wù)完成 latch.await(); // 這會(huì)阻塞當(dāng)前線程,直到計(jì)數(shù)器的值變?yōu)? // 關(guān)閉線程池 executor.shutdown(); // 所有任務(wù)都已完成,繼續(xù)執(zhí)行后續(xù)操作 System.out.println("All tasks are completed."); } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)CountDownLatch實(shí)例,其計(jì)數(shù)器的初始值為2。然后,我們提交了兩個(gè)任務(wù)到線程池中執(zhí)行。每個(gè)任務(wù)在執(zhí)行完成后都會(huì)調(diào)用latch.countDown()來(lái)將計(jì)數(shù)器的值減1。主線程通過調(diào)用latch.await()來(lái)等待,直到計(jì)數(shù)器的值變?yōu)?,這表示所有任務(wù)都已經(jīng)完成。然后,主線程可以繼續(xù)執(zhí)行后續(xù)操作。
CyclicBarrier(循環(huán)屏障)
CyclicBarrier是一種同步輔助類,它用于讓一組線程相互等待,直到到達(dá)某個(gè)公共屏障點(diǎn)。類似于CountDownLatch,CyclicBarrier也是直接作為一個(gè)類存在,而不是通過接口或繼承關(guān)系實(shí)現(xiàn)的。
在所有線程都到達(dá)屏障點(diǎn)之前,它們將在屏障點(diǎn)處阻塞。當(dāng)最后一個(gè)線程到達(dá)屏障點(diǎn)時(shí),屏障會(huì)打開,此時(shí)所有線程都將被釋放并繼續(xù)執(zhí)行。
使用CyclicBarrier(循環(huán)屏障)的示例
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { // 創(chuàng)建一個(gè)CyclicBarrier對(duì)象,設(shè)置屏障點(diǎn)需要等待的線程數(shù)量為3 // 第二個(gè)參數(shù)是一個(gè)Runnable對(duì)象,它是當(dāng)所有線程都到達(dá)屏障點(diǎn)時(shí)會(huì)執(zhí)行的任務(wù)(可選) private final CyclicBarrier barrier = new CyclicBarrier(3, () -> { System.out.println("All threads have reached the barrier. Barrier is now broken."); }); public void task() { // 模擬任務(wù)執(zhí)行前的準(zhǔn)備工作 try { // 假設(shè)每個(gè)線程都需要一些時(shí)間來(lái)準(zhǔn)備 Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " is ready."); // 等待直到所有線程都到達(dá)屏障點(diǎn) barrier.await(); // 所有線程都到達(dá)屏障點(diǎn)后,繼續(xù)執(zhí)行后續(xù)任務(wù) System.out.println(Thread.currentThread().getName() + " is continuing after the barrier."); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } public static void main(String[] args) { CyclicBarrierExample example = new CyclicBarrierExample(); // 創(chuàng)建并啟動(dòng)三個(gè)線程來(lái)執(zhí)行任務(wù) for (int i = 1; i <= 3; i++) { new Thread(() -> { example.task(); }, "Thread-" + i).start(); } } }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)CyclicBarrier對(duì)象,其參數(shù)為3,表示需要等待3個(gè)線程都到達(dá)屏障點(diǎn)。我們還提供了一個(gè)Runnable對(duì)象作為可選參數(shù),它將在所有線程都到達(dá)屏障點(diǎn)時(shí)執(zhí)行。
然后,我們定義了一個(gè)task()方法,該方法模擬了線程在到達(dá)屏障點(diǎn)之前的準(zhǔn)備工作,并調(diào)用了barrier.await()來(lái)等待其他線程。一旦所有線程都調(diào)用了await()方法并成功到達(dá)屏障點(diǎn),屏障就會(huì)被打開,所有線程都會(huì)繼續(xù)執(zhí)行await()之后的代碼。
在main()方法中,我們創(chuàng)建了三個(gè)線程來(lái)執(zhí)行task()方法,并啟動(dòng)了它們。你會(huì)注意到,每個(gè)線程都會(huì)打印一條消息表示它已準(zhǔn)備好,然后等待其他線程。一旦所有線程都到達(dá)屏障點(diǎn),它們會(huì)一起繼續(xù)執(zhí)行后續(xù)任務(wù),并打印相應(yīng)的消息。
這個(gè)示例展示了CyclicBarrier如何用于同步一組線程的執(zhí)行,直到它們都到達(dá)某個(gè)公共點(diǎn)。
鎖機(jī)制
鎖機(jī)制是計(jì)算機(jī)編程中一種用于控制對(duì)共享資源訪問的重要同步機(jī)制。在并發(fā)編程中,多個(gè)線程或進(jìn)程可能會(huì)同時(shí)嘗試訪問同一資源,這就有可能導(dǎo)致數(shù)據(jù)不一致、競(jìng)態(tài)條件等問題。鎖機(jī)制通過確保在同一時(shí)間只有一個(gè)線程或進(jìn)程能夠訪問某個(gè)資源,從而避免了這些問題。Java中主要有兩種鎖機(jī)制:
內(nèi)置鎖(Synchronized Locks)
通過synchronized關(guān)鍵字實(shí)現(xiàn),可以修飾方法或代碼塊。當(dāng)一個(gè)線程訪問某個(gè)對(duì)象的synchronized方法或代碼塊時(shí),它會(huì)先嘗試獲取該對(duì)象的鎖;如果鎖已被其他線程持有,則該線程會(huì)阻塞,直到鎖被釋放。
顯式鎖(Explicit Locks)
從Java 1.5開始,引入了java.util.concurrent.locks包,提供了比synchronized更靈活的鎖機(jī)制,如ReentrantLock。顯式鎖允許更復(fù)雜的同步控制,如嘗試非阻塞地獲取鎖、可中斷地獲取鎖以及嘗試獲取鎖時(shí)設(shè)置超時(shí)等。
Java中提供了多種鎖機(jī)制,例如:
- synchronized關(guān)鍵字:Java內(nèi)置的同步機(jī)制,可以修飾方法或代碼塊。當(dāng)一個(gè)線程訪問某個(gè)對(duì)象的synchronized方法或代碼塊時(shí),它會(huì)獲得該對(duì)象的鎖,其他線程必須等待鎖被釋放后才能訪問。
- ReentrantLock:這是java.util.concurrent.locks包下的一個(gè)類,提供了比synchronized更靈活的鎖定操作。它支持顯式地鎖定和解鎖操作,也支持嘗試鎖定、定時(shí)鎖定和可中斷的鎖定。
- ReadWriteLock:讀寫鎖,它允許多個(gè)讀線程同時(shí)訪問資源,但在寫線程訪問資源時(shí),會(huì)排斥其他所有讀線程和寫線程。這對(duì)于讀操作遠(yuǎn)多于寫操作的場(chǎng)景非常有用,可以顯著提高性能。
- StampedLock:Java 8中引入的一種鎖,它提供了對(duì)讀寫鎖的更優(yōu)支持,以及一種樂觀讀鎖的模式。與ReadWriteLock相比,StampedLock在某些情況下可以提供更高的吞吐量。
鎖機(jī)制的選擇取決于具體的應(yīng)用場(chǎng)景和性能要求。例如,在需要簡(jiǎn)單且自動(dòng)管理的鎖時(shí),可以使用synchronized;在需要更靈活的控制時(shí),可以使用ReentrantLock;在讀多寫少的場(chǎng)景下,ReadWriteLock或StampedLock可能是更好的選擇。
總的來(lái)說(shuō),鎖機(jī)制是并發(fā)編程中不可或缺的一部分,它們幫助程序員管理對(duì)共享資源的訪問,從而避免了并發(fā)編程中常見的問題。
JMM和鎖機(jī)制的關(guān)系
JMM和鎖機(jī)制在Java并發(fā)編程中相互協(xié)作,共同確保多線程程序的正確性和高效性。JMM定義了線程間如何共享和訪問變量,而鎖機(jī)制則提供了一種同步機(jī)制,確保在并發(fā)環(huán)境下對(duì)共享資源的訪問是安全的。
- 變量可見性:通過鎖機(jī)制(如synchronized塊或volatile變量)可以確保一個(gè)線程對(duì)共享變量的修改能夠被其他線程看到。
- 互斥訪問:鎖機(jī)制通過互斥地訪問共享資源來(lái)防止數(shù)據(jù)競(jìng)爭(zhēng)和不一致性的發(fā)生。
總的來(lái)說(shuō)
Java內(nèi)存模型和鎖機(jī)制是Java并發(fā)編程的基石,理解和掌握它們對(duì)于編寫高效、可維護(hù)的并發(fā)程序至關(guān)重要。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java空指針異常NullPointerException的原因與解決方案
在Java開發(fā)中,NullPointerException(空指針異常)是最常見的運(yùn)行時(shí)異常之一,通常發(fā)生在程序嘗試訪問或操作一個(gè)為null的對(duì)象引用時(shí),這種異常不僅會(huì)導(dǎo)致程序崩潰,還會(huì)增加調(diào)試難度,所以本文系統(tǒng)梳理NullPointerException的成因、調(diào)試方法和避免策略2025-06-06基于Protobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序
下面小編就為大家?guī)?lái)一篇基于Protobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-07-07SpringBoot2零基礎(chǔ)到精通之映射與常用注解請(qǐng)求處理
SpringBoot是一種整合Spring技術(shù)棧的方式(或者說(shuō)是框架),同時(shí)也是簡(jiǎn)化Spring的一種快速開發(fā)的腳手架,本篇讓我們一起學(xué)習(xí)映射、常用注解和方法參數(shù)的小技巧2022-03-03SpringCloud Feign遠(yuǎn)程調(diào)用實(shí)現(xiàn)詳解
Feign是Netflix公司開發(fā)的一個(gè)聲明式的REST調(diào)用客戶端; Ribbon負(fù)載均衡、 Hystrⅸ服務(wù)熔斷是我們Spring Cloud中進(jìn)行微服務(wù)開發(fā)非?;A(chǔ)的組件,在使用的過程中我們也發(fā)現(xiàn)它們一般都是同時(shí)出現(xiàn)的,而且配置也都非常相似2022-11-11SpringBoot中讀取application.properties配置文件的方法
這篇文章主要介紹了SpringBoot中讀取application.properties配置文件的三種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02java面試常問的Runnable和Callable的區(qū)別
大家好,本篇文章主要講的是java面試常問的Runnable和Callable的區(qū)別,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01