Java鎖機(jī)制的使用與實(shí)戰(zhàn)分析
引言
在多線程編程中,確保數(shù)據(jù)的并發(fā)訪問(wèn)安全是至關(guān)重要的。
Java作為支持多線程編程的主流語(yǔ)言,提供了多種內(nèi)置和高級(jí)鎖機(jī)制來(lái)解決共享資源的競(jìng)爭(zhēng)問(wèn)題,從而保證線程間的同步與協(xié)作。
本文將全面探討Java中的鎖機(jī)制,從基本概念、類型到具體實(shí)現(xiàn)方法,并結(jié)合實(shí)際應(yīng)用場(chǎng)景進(jìn)行說(shuō)明。
一、synchronized關(guān)鍵字
Java中的synchronized關(guān)鍵字是一種內(nèi)置鎖機(jī)制,用于保證多線程環(huán)境下的線程安全。它提供了簡(jiǎn)單易用的互斥訪問(wèn)控制,確保同一時(shí)刻只有一個(gè)線程可以執(zhí)行特定代碼塊。
下面是一個(gè)使用synchronized關(guān)鍵字的示例代碼:
public class SynchronizedExample {
private int counter;
public synchronized void increment() {
counter++;
}
public synchronized int getCount() {
return counter;
}
}
在上面的示例中,increment() 和 getCount() 方法都使用了 synchronized 關(guān)鍵字。這意味著當(dāng)一個(gè)線程正在執(zhí)行其中一個(gè)方法時(shí),其他線程想要執(zhí)行另一個(gè)方法將被阻塞,直到當(dāng)前線程執(zhí)行完畢并釋放鎖。
synchronized 關(guān)鍵字可以用于以下場(chǎng)景:
- 修飾實(shí)例方法:如示例中的 increment() 和 getCount() 方法。當(dāng)一個(gè)線程訪問(wèn)某個(gè)對(duì)象的同步方法時(shí),其他線程對(duì)該對(duì)象的其他同步方法訪問(wèn)將被阻塞。
- 修飾靜態(tài)方法:可以使用 synchronized 關(guān)鍵字修飾靜態(tài)方法,以實(shí)現(xiàn)對(duì)整個(gè)類的同步訪問(wèn)。
public class SynchronizedStaticExample {
private static int counter;
public static synchronized void increment() {
counter++;
}
public static synchronized int getCount() {
return counter;
}
}
- 修飾代碼塊:可以使用 synchronized 關(guān)鍵字修飾代碼塊,以實(shí)現(xiàn)對(duì)特定資源的同步訪問(wèn)。
public class SynchronizedBlockExample {
private int counter;
private Object lock = new Object();
public void increment() {
synchronized(lock) {
counter++;
}
}
public int getCount() {
synchronized(lock) {
return counter;
}
}
}
在上面的示例中,increment() 和 getCount() 方法內(nèi)部的同步代碼塊使用了同一個(gè)鎖對(duì)象 lock。
這意味著當(dāng)一個(gè)線程訪問(wèn)其中一個(gè)同步代碼塊時(shí),其他線程想要訪問(wèn)另一個(gè)同步代碼塊將被阻塞,直到當(dāng)前線程執(zhí)行完畢并釋放鎖。
二、內(nèi)置鎖(Intrinsic Locks or Monitor Locks)
Java內(nèi)置鎖(Intrinsic Locks 或 Monitor Locks)是基于JVM實(shí)現(xiàn)的一種同步機(jī)制,每個(gè)Java對(duì)象都可以關(guān)聯(lián)一個(gè)內(nèi)置鎖。
當(dāng)線程試圖訪問(wèn)被 synchronized 關(guān)鍵字修飾的方法或代碼塊時(shí),會(huì)嘗試獲取該對(duì)象的內(nèi)置鎖,如果成功,則可以執(zhí)行相應(yīng)的臨界區(qū)代碼;如果失?。存i已被其他線程持有),則當(dāng)前線程將進(jìn)入阻塞狀態(tài),等待鎖釋放。
以下是一個(gè)使用Java內(nèi)置鎖的示例代碼:
public class IntrinsicLockExample {
private int counter = 0;
// 同步實(shí)例方法,隱式使用 this 對(duì)象作為鎖
public synchronized void increment() {
counter++;
}
// 同步代碼塊,顯式指定鎖對(duì)象
public void incrementWithBlock(Object lock) {
synchronized (lock) {
counter++;
}
}
// 獲取當(dāng)前計(jì)數(shù)
public synchronized int getCount() {
return counter;
}
// 示例類中的靜態(tài)變量和對(duì)應(yīng)的同步方法
private static int staticCounter = 0;
public static synchronized void incrementStatic() {
staticCounter++;
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
IntrinsicLockExample example = new IntrinsicLockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + example.getCount()); // 輸出結(jié)果應(yīng)為2000
// 靜態(tài)成員的同步示例
for (int i = 0; i < 500; i++) {
IntrinsicLockExample.incrementStatic();
}
System.out.println("Final static count: " + IntrinsicLockExample.staticCounter); // 輸出結(jié)果應(yīng)為500
}
}
詳細(xì)解釋:
- 在 increment() 方法中,使用了 synchronized 關(guān)鍵字修飾,這意味著每次只有一個(gè)線程可以執(zhí)行這個(gè)方法,確保了對(duì) counter 變量的并發(fā)訪問(wèn)安全。
- incrementWithBlock() 方法展示了如何在代碼塊級(jí)別使用內(nèi)置鎖,通過(guò)傳入一個(gè) Object 類型的鎖參數(shù),多個(gè)方法可以共享同一把鎖,達(dá)到同步的目的。
- getCount() 方法同樣使用了 synchronized,保證了讀取 counter 的線程安全。
- incrementStatic() 是一個(gè)同步靜態(tài)方法,它使用的是類級(jí)別的內(nèi)置鎖,因此在同一時(shí)刻只能有一個(gè)線程修改 staticCounter 變量。
三、顯示鎖(Lock)
Java顯示鎖(顯示鎖通常指的是java.util.concurrent.locks.Lock接口及其實(shí)現(xiàn)類)提供了比內(nèi)置鎖(synchronized關(guān)鍵字)更強(qiáng)大和靈活的線程同步機(jī)制。顯示鎖允許程序員更加精確地控制線程的加鎖和解鎖行為,支持中斷請(qǐng)求,以及非阻塞式的嘗試獲取鎖等特性。
以下是使用java.util.concurrent.locks.ReentrantLock作為顯示鎖的一個(gè)示例代碼:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DisplayedLockExample {
private final Lock lock = new ReentrantLock();
private int counter;
public void increment() {
lock.lock(); // 加鎖
try {
counter++;
} finally {
lock.unlock(); // 無(wú)論何時(shí)都要確保解鎖
}
}
public int getCount() {
lock.lock(); // 同樣需要加鎖保護(hù)
try {
return counter;
} finally {
lock.unlock();
}
}
// 顯示鎖還提供了更多的控制方式,比如嘗試獲取鎖,支持中斷等
public void tryIncrementWithTimeout(int timeout, TimeUnit unit) throws InterruptedException {
if (lock.tryLock(timeout, unit)) {
try {
counter++;
} finally {
lock.unlock();
}
} else {
System.out.println("未能在規(guī)定時(shí)間內(nèi)獲取到鎖");
}
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
DisplayedLockExample example = new DisplayedLockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + example.getCount()); // 輸出結(jié)果應(yīng)為2000
}
}
詳細(xì)解釋:
- DisplayedLockExample 類中定義了一個(gè) ReentrantLock 對(duì)象作為顯示鎖。
- increment() 和 getCount() 方法在修改或讀取 counter 變量時(shí),均首先獲取鎖,然后在 finally 語(yǔ)句塊中確保無(wú)論如何都能釋放鎖,這是一種最佳實(shí)踐,確保即使在異常情況下也能正確釋放鎖。
- tryIncrementWithTimeout() 方法演示了顯示鎖的超時(shí)獲取功能,它嘗試在指定時(shí)間內(nèi)獲取鎖,若超過(guò)設(shè)定時(shí)間仍無(wú)法獲取,則不再等待并繼續(xù)執(zhí)行后續(xù)邏輯。
顯示鎖相比內(nèi)置鎖的優(yōu)勢(shì):
- 可以嘗試非阻塞地獲取鎖,比如 tryLock() 和 tryLock(long timeout, TimeUnit unit) 方法。
- 支持中斷,線程在等待鎖時(shí)可以響應(yīng)中斷請(qǐng)求,這對(duì)于那些需要取消長(zhǎng)時(shí)間等待的任務(wù)非常有用。
- 可以實(shí)現(xiàn)公平鎖策略,即按照線程請(qǐng)求鎖的順序來(lái)分配鎖,避免“饑餓”現(xiàn)象。
- 提供了鎖的監(jiān)聽和喚醒機(jī)制,可通過(guò) Condition 對(duì)象實(shí)現(xiàn)更復(fù)雜的同步結(jié)構(gòu)
四、讀寫鎖(Read-Write Locks)
Java的讀寫鎖(Read-Write Locks)是一種特殊的鎖機(jī)制,它允許多個(gè)讀取者同時(shí)訪問(wèn)共享資源,但在寫入者訪問(wèn)時(shí)會(huì)排斥所有讀取者和其他寫入者。這使得在讀多寫少的情況下,系統(tǒng)的并發(fā)性能得到顯著提升。Java中實(shí)現(xiàn)讀寫鎖的主要類是java.util.concurrent.locks.ReadWriteLock,以及它的標(biāo)準(zhǔn)實(shí)現(xiàn)java.util.concurrent.locks.ReentrantReadWriteLock。
以下是一個(gè)使用ReentrantReadWriteLock的示例代碼及其詳細(xì)解釋:
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private int sharedResource;
public void read() {
readLock.lock(); // 獲取讀鎖
try {
// 多個(gè)線程可以同時(shí)在這里讀取數(shù)據(jù)
System.out.println("Reading the resource: " + sharedResource);
} finally {
readLock.unlock(); // 釋放讀鎖
}
}
public void update() {
writeLock.lock(); // 獲取寫鎖
try {
// 當(dāng)有線程在執(zhí)行這里的寫操作時(shí),其他所有讀寫線程都會(huì)被阻塞
sharedResource++;
System.out.println("Updated the resource to: " + sharedResource);
} finally {
writeLock.unlock(); // 釋放寫鎖
}
}
// 使用示例
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
ExecutorService executor = Executors.newFixedThreadPool(10);
// 創(chuàng)建大量讀取任務(wù)
for (int i = 0; i < 20; i++) {
executor.submit(() -> example.read());
}
// 創(chuàng)建少量寫入任務(wù)
for (int i = 0; i < 5; i++) {
executor.submit(() -> example.update());
}
// 關(guān)閉線程池
executor.shutdown();
}
}
詳細(xì)解釋:
- ReadWriteLockExample 類中定義了一個(gè) ReentrantReadWriteLock 對(duì)象,并從中提取出讀鎖 readLock 和寫鎖 writeLock。
- read() 方法獲取讀鎖后讀取共享資源 sharedResource,此時(shí)如果有多個(gè)線程同時(shí)調(diào)用 read() 方法,它們可以同時(shí)執(zhí)行,因?yàn)樽x鎖是共享的。
- update() 方法獲取寫鎖后更新 sharedResource,在執(zhí)行寫操作時(shí),所有其他嘗試獲取讀鎖或?qū)戞i的線程都會(huì)被阻塞,直到寫操作完成并釋放寫鎖為止。
- 在主方法中,我們創(chuàng)建了一個(gè)線程池,提交了大量的讀任務(wù)和少量的寫任務(wù),模擬了讀多寫少的場(chǎng)景,這時(shí)讀寫鎖可以有效提高系統(tǒng)并發(fā)性能。
五、條件變量(Condition Objects)
在Java中,條件變量是通過(guò)java.util.concurrent.locks.Condition接口實(shí)現(xiàn)的,它與鎖(如ReentrantLock)一起使用,允許線程等待滿足特定條件時(shí)被喚醒。下面是一個(gè)使用Condition對(duì)象的示例代碼及詳細(xì)解釋:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionVariableExample {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
// 共享資源,模擬隊(duì)列為空的情況
private boolean isEmpty = true;
public void produce() {
lock.lock();
try {
// 當(dāng)隊(duì)列為空時(shí),生產(chǎn)者線程等待condition被signal
while (!isEmpty) {
condition.await(); // 線程在此處釋放鎖并進(jìn)入等待狀態(tài)
}
// 生產(chǎn)商品邏輯...
System.out.println("Produced an item, queue is no longer empty.");
isEmpty = false; // 更新條件
// 喚醒所有等待此condition的消費(fèi)者線程
condition.signalAll();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock(); // 不論如何都要確保解鎖
}
}
public void consume() {
lock.lock();
try {
// 當(dāng)隊(duì)列非空時(shí),消費(fèi)者線程等待condition被signal
while (isEmpty) {
condition.await(); // 線程在此處釋放鎖并進(jìn)入等待狀態(tài)
}
// 消費(fèi)商品邏輯...
System.out.println("Consumed an item, queue is now empty.");
isEmpty = true; // 更新條件
// 喚醒所有等待此condition的生產(chǎn)者線程
condition.signalAll();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock(); // 不論如何都要確保解鎖
}
}
// 使用示例
public static void main(String[] args) throws InterruptedException {
ConditionVariableExample example = new ConditionVariableExample();
Thread producer = new Thread(example::produce);
Thread consumer = new Thread(example::consume);
producer.start();
consumer.start();
// 確保生產(chǎn)者先啟動(dòng)并改變條件
Thread.sleep(100);
example.produce(); // 手動(dòng)調(diào)用一次生產(chǎn)方法,以初始化條件變化
producer.join();
consumer.join();
}
}
詳細(xì)解釋:
- ConditionVariableExample 類中定義了一個(gè) ReentrantLock 對(duì)象和從該鎖對(duì)象創(chuàng)建的一個(gè) Condition 對(duì)象。
- 在 produce() 方法中,當(dāng)共享資源(這里用 isEmpty 標(biāo)記隊(duì)列是否為空)為真(即隊(duì)列為空)時(shí),生產(chǎn)者線程調(diào)用 condition.await() 方法進(jìn)入等待狀態(tài),并釋放鎖。這樣,其他線程可以修改 isEmpty 的值。
- 當(dāng) consume() 方法中的消費(fèi)者線程檢測(cè)到 isEmpty 為假(即隊(duì)列非空),則消費(fèi)者線程調(diào)用 condition.await() 進(jìn)入等待狀態(tài)。
- 當(dāng)條件發(fā)生變化時(shí),比如生產(chǎn)者生產(chǎn)了物品使得隊(duì)列不再為空,則會(huì)調(diào)用 condition.signalAll() 來(lái)喚醒所有等待該條件的線程。
- 注意,每個(gè)方法都在 try-finally 結(jié)構(gòu)中管理鎖的加鎖和解鎖操作,確保即使在異常情況下也能正確釋放鎖。
六、樂觀鎖(Optimistic Locking)
樂觀鎖在Java中的實(shí)現(xiàn)通常依賴于原子變量類(如java.util.concurrent.atomic包下的類)或數(shù)據(jù)庫(kù)事務(wù)中的版本號(hào)機(jī)制。樂觀鎖的假設(shè)是大多數(shù)情況下數(shù)據(jù)不會(huì)發(fā)生沖突,因此在修改數(shù)據(jù)前并不加鎖,而是在更新時(shí)檢查在此期間是否有其他線程修改過(guò)該數(shù)據(jù)。如果發(fā)現(xiàn)數(shù)據(jù)未被修改,則執(zhí)行更新操作;否則則需要重新讀取、驗(yàn)證并嘗試更新。
以下是一個(gè)使用AtomicInteger作為樂觀鎖機(jī)制實(shí)現(xiàn)的簡(jiǎn)單示例:
import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockingExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
// 讀取當(dāng)前值
int currentValue;
do {
// 獲取一個(gè)可能過(guò)時(shí)的值
currentValue = counter.get();
// 檢查在此期間是否已經(jīng)被其他線程修改過(guò)
} while (!counter.compareAndSet(currentValue, currentValue + 1)); // 如果當(dāng)前值未變,則更新為原值+1
System.out.println("Counter incremented to: " + counter.get());
}
public static void main(String[] args) {
OptimisticLockingExample example = new OptimisticLockingExample();
Thread thread1 = new Thread(example::increment);
Thread thread2 = new Thread(example::increment);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
詳細(xì)解釋:
- 在這個(gè)例子中,我們使用了AtomicInteger來(lái)模擬一個(gè)計(jì)數(shù)器,它具有原子性的get()和compareAndSet()方法。
- compareAndSet()方法(也稱為CAS操作)嘗試將當(dāng)前值與預(yù)期值進(jìn)行比較,如果當(dāng)前值等于預(yù)期值,則以原子方式更新為新值。這里的預(yù)期值就是我們?cè)谘h(huán)開始時(shí)獲取到的currentValue。
- 當(dāng)多個(gè)線程同時(shí)調(diào)用increment()方法時(shí),它們都會(huì)嘗試更新計(jì)數(shù)器。如果在某一線程嘗試更新之前,計(jì)數(shù)器已被其他線程更新,則其compareAndSet()會(huì)失敗,并且該線程將繼續(xù)下一次循環(huán),再次獲取最新的currentValue并嘗試更新。
- 這樣,在并發(fā)環(huán)境下,樂觀鎖通過(guò)不斷的重試確保了最終只有一個(gè)線程成功地進(jìn)行了原子性更新,從而實(shí)現(xiàn)了線程安全的計(jì)數(shù)操作。
七、悲觀鎖(Pessimistic Locking)
悲觀鎖在Java中通常表現(xiàn)為獲取到一個(gè)鎖后,其他線程嘗試訪問(wèn)該資源時(shí)會(huì)立即被阻塞,直到持有鎖的線程釋放鎖。最直接的例子就是使用synchronized關(guān)鍵字修飾的方法或代碼塊。但為了更好地說(shuō)明數(shù)據(jù)庫(kù)層面的悲觀鎖實(shí)現(xiàn),這里提供一個(gè)基于JDBC和Hibernate的示例:
JDBC示例(通過(guò)SQL的SELECT ... FOR UPDATE實(shí)現(xiàn)悲觀鎖):
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class PessimisticLockingExampleJDBC {
private static final String URL = "jdbc:mysql://localhost:3306/mydb";
private static final String USER = "root";
private static final String PASSWORD = "password";
public void updateDataWithPessimisticLock(int id) {
try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
connection.setAutoCommit(false); // 關(guān)閉自動(dòng)提交,開始事務(wù)
// 使用FOR UPDATE來(lái)獲取悲觀鎖
String sql = "SELECT * FROM my_table WHERE id = ? FOR UPDATE";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, id);
preparedStatement.execute();
// 假設(shè)我們從查詢結(jié)果中獲取了數(shù)據(jù),并準(zhǔn)備更新它...
// 更新操作
String updateSql = "UPDATE my_table SET column = ? WHERE id = ?";
preparedStatement = connection.prepareStatement(updateSql);
preparedStatement.setString(1, "new_value");
preparedStatement.setInt(2, id);
preparedStatement.executeUpdate();
connection.commit(); // 提交事務(wù),釋放鎖
} catch (SQLException e) {
// 處理異常并回滾事務(wù)
try {
if (connection != null) {
connection.rollback();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}
}
}
Hibernate示例(通過(guò)Session的鎖定方法實(shí)現(xiàn)悲觀鎖):
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class PessimisticLockingExampleHibernate {
private SessionFactory sessionFactory;
public PessimisticLockingExampleHibernate() {
Configuration configuration = new Configuration().configure();
sessionFactory = configuration.buildSessionFactory();
}
public void updateDataWithPessimisticLock(Integer id) {
try (Session session = sessionFactory.openSession()) {
// 開始事務(wù)
session.beginTransaction();
// 加載實(shí)體并顯式地請(qǐng)求悲觀鎖
MyEntity entity = session.get(MyEntity.class, id, LockMode.PESSIMISTIC_WRITE);
// 假設(shè)我們?cè)谶@里修改了entity的屬性值...
entity.setProperty("new_value");
// 提交事務(wù),同時(shí)釋放鎖
session.getTransaction().commit();
} catch (Exception e) {
// 如果發(fā)生異常,則回滾事務(wù)
if (session != null && session.getTransaction() != null && session.getTransaction().isActive()) {
session.getTransaction().rollback();
}
e.printStackTrace();
}
}
}
@Entity
@Table(name = "my_table")
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 其他屬性及getter、setter省略...
}
詳細(xì)解釋:
- 在JDBC示例中,通過(guò)設(shè)置事務(wù)并執(zhí)行SELECT ... FOR UPDATE語(yǔ)句,在讀取記錄的同時(shí)獲得了對(duì)該記錄的悲觀鎖,使得其他事務(wù)無(wú)法修改這條記錄,直至當(dāng)前事務(wù)結(jié)束。
- 在Hibernate示例中,通過(guò)session.get()方法加載實(shí)體對(duì)象時(shí)指定了LockMode.PESSIMISTIC_WRITE模式,這樣在獲取實(shí)體時(shí)即獲得了一把悲觀寫鎖,阻止其他事務(wù)對(duì)同一實(shí)體進(jìn)行并發(fā)修改。
八、自旋鎖(Spin Locks)
自旋鎖在Java中主要用于解決線程間短時(shí)間的同步問(wèn)題,尤其適用于等待時(shí)間極短并且CPU資源相對(duì)充足的場(chǎng)景。在Java中并沒有直接提供名為“自旋鎖”的API,但是可以通過(guò)循環(huán)和volatile關(guān)鍵字模擬實(shí)現(xiàn)自旋鎖的行為。以下是一個(gè)簡(jiǎn)單的自旋鎖示例:
public class SpinLock {
private volatile boolean isLocked = false;
public void lock() {
while (true) {
if (!isLocked) { // 當(dāng)鎖未被占用時(shí)
if (compareAndSet(false, true)) { // 使用CAS操作嘗試獲取鎖
break; // 成功獲取鎖后退出自旋
}
}
// 鎖被占用時(shí),繼續(xù)循環(huán)(自旋)
}
}
public void unlock() {
isLocked = false; // 釋放鎖
}
// 使用AtomicBoolean或Unsafe等工具類提供的原子操作來(lái)實(shí)現(xiàn)compareAndSet
private boolean compareAndSet(boolean expect, boolean update) {
return java.util.concurrent.atomic.AtomicBoolean.compareAndSet(this.isLocked, expect, update);
}
// 示例用法
public static void main(String[] args) {
final SpinLock spinLock = new SpinLock();
Thread t1 = new Thread(() -> {
spinLock.lock();
try {
System.out.println("Thread 1 acquired the lock");
Thread.sleep(1000); // 模擬執(zhí)行耗時(shí)任務(wù)
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
spinLock.unlock();
}
});
Thread t2 = new Thread(() -> {
spinLock.lock();
try {
System.out.println("Thread 2 acquired the lock");
} finally {
spinLock.unlock();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
詳細(xì)解釋:
- 在上述代碼中,我們創(chuàng)建了一個(gè)名為SpinLock的類,其中包含一個(gè)volatile布爾變量isLocked,用于表示鎖的狀態(tài)。
- lock()方法通過(guò)不斷地檢查并試圖更新isLocked變量來(lái)實(shí)現(xiàn)自旋。當(dāng)isLocked為false時(shí),使用compareAndSet()原子操作將其設(shè)置為true,如果成功則說(shuō)明當(dāng)前線程獲得了鎖,并跳出循環(huán)。
- unlock()方法將isLocked設(shè)置為false,以釋放鎖。
- compareAndSet()方法利用Java的原子性操作(這里假設(shè)使用了AtomicBoolean)確保對(duì)isLocked變量的修改是原子性的,這可以避免多線程環(huán)境下數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題。
九、StampedLock
Java中的StampedLock是Java 8引入的一個(gè)高性能的并發(fā)工具類,它提供了更靈活的讀寫鎖機(jī)制,包括悲觀讀鎖、樂觀讀鎖和寫鎖。每個(gè)鎖操作都會(huì)返回一個(gè)戳記(stamp),后續(xù)的操作可以通過(guò)這個(gè)戳記來(lái)驗(yàn)證或釋放鎖。
以下是一個(gè)使用StampedLock實(shí)現(xiàn)讀寫鎖的示例代碼及詳細(xì)解釋:
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock lock = new StampedLock();
// 共享資源
private int sharedResource;
public void read() {
long stamp = lock.readLock(); // 獲取悲觀讀鎖
try {
// 在此塊中可以安全地讀取sharedResource
System.out.println("Reading the resource: " + sharedResource);
} finally {
lock.unlockRead(stamp); // 釋放讀鎖
}
}
public void optimisticRead() {
long stamp = lock.tryOptimisticRead(); // 嘗試獲取樂觀讀鎖
int localCopy = sharedResource;
if (lock.validate(stamp)) { // 驗(yàn)證在獲取戳記后,數(shù)據(jù)是否被其他線程修改過(guò)
// 如果沒有被修改,則可以安全地使用localCopy
System.out.println("Optimistically reading the resource: " + localCopy);
} else {
// 如果有被修改,需要升級(jí)到悲觀讀鎖或者重新讀取
long newStamp = lock.readLock();
try {
// 現(xiàn)在可以安全地再次讀取資源
localCopy = sharedResource;
System.out.println("Upgraded to悲觀讀鎖, reading the resource: " + localCopy);
} finally {
lock.unlockRead(newStamp); // 釋放悲觀讀鎖
}
}
}
public void write(int newValue) {
long stamp = lock.writeLock(); // 獲取寫鎖
try {
// 在此塊中可以安全地更新sharedResource
sharedResource = newValue;
System.out.println("Updated the resource to: " + sharedResource);
} finally {
lock.unlockWrite(stamp); // 釋放寫鎖
}
}
// 使用示例
public static void main(String[] args) {
StampedLockExample example = new StampedLockExample();
// 創(chuàng)建并啟動(dòng)多個(gè)讀取者和寫入者線程...
}
}
詳細(xì)解釋:
1.StampedLock提供三種模式的鎖:
- 悲觀讀鎖:通過(guò)readLock()方法獲取,類似于ReentrantReadWriteLock中的讀鎖,當(dāng)有寫鎖持有時(shí),讀鎖會(huì)阻塞。
- 樂觀讀鎖:通過(guò)tryOptimisticRead()方法嘗試獲取,該方法立即返回,不會(huì)阻塞,但必須在之后調(diào)用validate(long stamp)方法確認(rèn)讀取期間是否有寫鎖發(fā)生改變,如果數(shù)據(jù)被修改則需升級(jí)為悲觀讀鎖。
- 寫鎖:通過(guò)writeLock()方法獲取,與ReentrantReadWriteLock類似,獨(dú)占鎖,不允許任何其他讀或?qū)戞i同時(shí)存在。
2.示例中read()方法展示了如何使用悲觀讀鎖進(jìn)行讀操作,在讀取期間阻止寫操作。
3.optimisticRead()方法首先嘗試樂觀讀鎖,然后檢查戳記的有效性。若數(shù)據(jù)未被更改,則可以直接使用本地緩存的值;否則,為了確保讀取到最新數(shù)據(jù),需要升級(jí)到悲觀讀鎖。
4.write()方法展示了如何使用寫鎖執(zhí)行寫操作,在寫入期間阻止所有其他讀寫操作。
總結(jié)
深入理解和熟練掌握J(rèn)ava中的鎖機(jī)制是構(gòu)建高效、穩(wěn)定多線程程序的關(guān)鍵所在。開發(fā)者應(yīng)依據(jù)具體的并發(fā)場(chǎng)景,權(quán)衡鎖的開銷與安全性,合理選擇并應(yīng)用合適的鎖實(shí)現(xiàn)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
JAVA使用動(dòng)態(tài)代理對(duì)象進(jìn)行敏感字過(guò)濾代碼實(shí)例
這篇文章主要介紹了JAVA使用動(dòng)態(tài)代理對(duì)象進(jìn)行敏感字過(guò)濾代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
Java簡(jiǎn)單有效實(shí)現(xiàn)將PDF轉(zhuǎn)換為TIFF圖片
在日常開發(fā)中,我們常需要將 PDF 轉(zhuǎn)換為高質(zhì)量的 TIFF 圖片,本文將通過(guò) Java 提供一個(gè)簡(jiǎn)單高效的解決方案,幫助你輕松完成 PDF 到 TIFF 的轉(zhuǎn)換,并支持批量與多頁(yè)處理,有需要的可以參考一下2025-09-09
SpringBoot整合MybatisPlus實(shí)現(xiàn)增刪改查功能
MybatisPlus是國(guó)產(chǎn)的第三方插件,?它封裝了許多常用的CURDapi,免去了我們寫mapper.xml的重復(fù)勞動(dòng)。本文將整合MybatisPlus實(shí)現(xiàn)增刪改查功能,感興趣的可以了解一下2022-05-05
解決IDEA修改 .vmoptions 文件后導(dǎo)致無(wú)法啟動(dòng)的問(wèn)題
這篇文章主要介紹了解決IDEA修改 .vmoptions 文件后導(dǎo)致無(wú)法啟動(dòng)的問(wèn)題,需要的朋友可以參考下2020-12-12
Spring aop 如何通過(guò)獲取代理對(duì)象實(shí)現(xiàn)事務(wù)切換
這篇文章主要介紹了Spring aop 如何通過(guò)獲取代理對(duì)象實(shí)現(xiàn)事務(wù)切換的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java為什么基本數(shù)據(jù)類型不需要進(jìn)行創(chuàng)建對(duì)象?
今天小編就為大家分享一篇關(guān)于Java為什么基本數(shù)據(jù)類型不需要進(jìn)行創(chuàng)建對(duì)象?,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-04-04
MyBatis實(shí)現(xiàn)高級(jí)映射的示例代碼
高級(jí)映射主要還是映射,只是映射中的數(shù)據(jù)關(guān)系復(fù)雜了,其中就包括一對(duì)一、一對(duì)多、多對(duì)多的關(guān)系,本文主要介紹了MyBatis實(shí)現(xiàn)高級(jí)映射的示例代碼,感興趣的可以了解一下2024-06-06
Mybatis-Plus如何使用分頁(yè)實(shí)例詳解
最近在研究mybatis,然后就去找簡(jiǎn)化mybatis開發(fā)的工具,下面這篇文章主要給大家介紹了關(guān)于Mybatis-Plus如何使用分頁(yè)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03

