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塊來保證。
- 有序性: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)存中重新讀取共享變量的值,來實(shí)現(xiàn)可見性。(大概就是:不同線程之間無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間的變量值的傳遞均需要通過主內(nèi)存來完成,當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改)
- 原子性:一個(gè)或多個(gè)操作在執(zhí)行過程中不會(huì)被其他線程中斷。Java內(nèi)存模型只保證基本數(shù)據(jù)類型的讀取和賦值是原子的,對(duì)于復(fù)合操作(如i++),則需要通過同步機(jī)制來保證原子性。(大概就是:一個(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語義,即程序執(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ī)制。它可以用來修飾方法或代碼塊,確保在同一時(shí)刻只有一個(gè)線程能夠執(zhí)行該方法或代碼塊。它通過鎖機(jī)制(通常是對(duì)象鎖或類鎖)來確保同一時(shí)刻只有一個(gè)線程能執(zhí)行某個(gè)方法或代碼塊。
修飾方法時(shí),它會(huì)自動(dòng)鎖定調(diào)用該方法的對(duì)象或該類(如果是靜態(tài)方法)。
修飾代碼塊時(shí),需要指定一個(gè)鎖對(duì)象,多個(gè)線程需要對(duì)該鎖對(duì)象進(jìn)行競爭,以獲得執(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è)線程來更新計(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è)線程來模擬運(yùn)行中的任務(wù)
public void startTask() {
Thread taskThread = new Thread(() -> {
while (running) {
// 模擬任務(wù)執(zhí)行,這里只是簡單地打印信息
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秒來模擬任務(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é)束,這里為了簡化示例而省略了
}
}在這個(gè)示例中,running變量被volatile修飾,這意味著當(dāng)running的值被修改時(shí),所有線程都會(huì)看到最新的值。在startTask方法中,我們啟動(dòng)了一個(gè)線程來模擬一個(gè)長時(shí)間運(yùn)行的任務(wù),該任務(wù)通過檢查running變量的值來決定是否繼續(xù)執(zhí)行。在主線程中,我們通過將running設(shè)置為false來停止任務(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è)簡單的示例中,沒有等待消費(fèi)者線程完成。
// 在實(shí)際應(yīng)用中,你可能需要某種形式的同步來確保生產(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)中并使用條件變量來檢查實(shí)際狀態(tài)是一種常見且推薦的做法。
此外,還需要注意,wait(), notify(), 和 notifyAll() 方法必須在同步代碼塊或同步方法中調(diào)用,因?yàn)樗鼈円蕾囉趯?duì)象鎖。在這個(gè)示例中,我們使用了一個(gè)單獨(dú)的鎖對(duì)象 lock 來控制對(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(); // 釋放鎖,無論是否發(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(); // 無論如何,最后都要釋放鎖
}
}
// 觸發(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()來喚醒它。同樣地,在調(diào)用這些方法之前,必須持有與Condition對(duì)象相關(guān)聯(lián)的鎖。這是通過調(diào)用lock.lock()來完成的,并且在方法結(jié)束時(shí)通過lock.unlock()來釋放鎖,以確保鎖的釋放總是會(huì)發(fā)生,無論方法是否成功執(zhí)行。
Semaphore(信號(hào)量)
Semaphore是一個(gè)計(jì)數(shù)信號(hào)量,是一種基于計(jì)數(shù)的同步機(jī)制,可以用來控制同時(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è)線程來訪問資源
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方法來模擬對(duì)資源的訪問。在訪問資源之前,線程會(huì)嘗試通過調(diào)用semaphore.acquire()來獲取許可。由于許可數(shù)量被限制為2,因此同時(shí)最多只有兩個(gè)線程能夠進(jìn)入accessResource方法的主體部分。一旦線程完成了對(duì)資源的訪問,它就會(huì)通過調(diào)用semaphore.release()來釋放許可,從而允許其他等待的線程獲取許可并訪問資源。
運(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è)線程池來執(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秒來完成
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秒來完成
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()來將計(jì)數(shù)器的值減1。主線程通過調(diào)用latch.await()來等待,直到計(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í)間來準(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è)線程來執(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()來等待其他線程。一旦所有線程都調(diào)用了await()方法并成功到達(dá)屏障點(diǎn),屏障就會(huì)被打開,所有線程都會(huì)繼續(xù)執(zhí)行await()之后的代碼。
在main()方法中,我們創(chuàng)建了三個(gè)線程來執(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ù)不一致、競態(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)多于寫操作的場景非常有用,可以顯著提高性能。
- StampedLock:Java 8中引入的一種鎖,它提供了對(duì)讀寫鎖的更優(yōu)支持,以及一種樂觀讀鎖的模式。與ReadWriteLock相比,StampedLock在某些情況下可以提供更高的吞吐量。
鎖機(jī)制的選擇取決于具體的應(yīng)用場景和性能要求。例如,在需要簡單且自動(dòng)管理的鎖時(shí),可以使用synchronized;在需要更靈活的控制時(shí),可以使用ReentrantLock;在讀多寫少的場景下,ReadWriteLock或StampedLock可能是更好的選擇。
總的來說,鎖機(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ī)制通過互斥地訪問共享資源來防止數(shù)據(jù)競爭和不一致性的發(fā)生。
總的來說
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ī)硪黄赑rotobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07
SpringBoot2零基礎(chǔ)到精通之映射與常用注解請(qǐng)求處理
SpringBoot是一種整合Spring技術(shù)棧的方式(或者說是框架),同時(shí)也是簡化Spring的一種快速開發(fā)的腳手架,本篇讓我們一起學(xué)習(xí)映射、常用注解和方法參數(shù)的小技巧2022-03-03
SpringCloud 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-11
SpringBoot中讀取application.properties配置文件的方法
這篇文章主要介紹了SpringBoot中讀取application.properties配置文件的三種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02
java面試常問的Runnable和Callable的區(qū)別
大家好,本篇文章主要講的是java面試常問的Runnable和Callable的區(qū)別,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01

