Java鎖機(jī)制詳細(xì)講解與實(shí)戰(zhàn)應(yīng)用
引言
在多線程編程中,確保數(shù)據(jù)的并發(fā)訪問安全是至關(guān)重要的。Java作為支持多線程編程的主流語言,提供了多種內(nèi)置和高級(jí)鎖機(jī)制來解決共享資源的競爭問題,從而保證線程間的同步與協(xié)作。本文將全面探討Java中的鎖機(jī)制,從基本概念、類型到具體實(shí)現(xiàn)方法,并結(jié)合實(shí)際應(yīng)用場景進(jìn)行說明。
一、synchronized關(guān)鍵字
Java中的synchronized關(guān)鍵字是一種內(nèi)置鎖機(jī)制,用于保證多線程環(huá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)鍵字可以用于以下場景:
- 修飾實(shí)例方法:如示例中的 increment() 和 getCount() 方法。當(dāng)一個(gè)線程訪問某個(gè)對象的同步方法時(shí),其他線程對該對象的其他同步方法訪問將被阻塞。
- 修飾靜態(tài)方法:可以使用 synchronized 關(guān)鍵字修飾靜態(tài)方法,以實(shí)現(xiàn)對整個(gè)類的同步訪問。
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)對特定資源的同步訪問。
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è)鎖對象 lock。這意味著當(dāng)一個(gè)線程訪問其中一個(gè)同步代碼塊時(shí),其他線程想要訪問另一個(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對象都可以關(guān)聯(lián)一個(gè)內(nèi)置鎖。當(dāng)線程試圖訪問被 synchronized 關(guān)鍵字修飾的方法或代碼塊時(shí),會(huì)嘗試獲取該對象的內(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 對象作為鎖
public synchronized void increment() {
counter++;
}
// 同步代碼塊,顯式指定鎖對象
public void incrementWithBlock(Object lock) {
synchronized (lock) {
counter++;
}
}
// 獲取當(dāng)前計(jì)數(shù)
public synchronized int getCount() {
return counter;
}
// 示例類中的靜態(tài)變量和對應(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è)方法,確保了對 counter 變量的并發(fā)訪問安全。
- incrementWithBlock() 方法展示了如何在代碼塊級(jí)別使用內(nèi)置鎖,通過傳入一個(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ī)制。顯示鎖允許程序員更加精確地控制線程的加鎖和解鎖行為,支持中斷請求,以及非阻塞式的嘗試獲取鎖等特性。
以下是使用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(); // 無論何時(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 對象作為顯示鎖。
- increment() 和 getCount() 方法在修改或讀取 counter 變量時(shí),均首先獲取鎖,然后在 finally 語句塊中確保無論如何都能釋放鎖,這是一種最佳實(shí)踐,確保即使在異常情況下也能正確釋放鎖。
- tryIncrementWithTimeout() 方法演示了顯示鎖的超時(shí)獲取功能,它嘗試在指定時(shí)間內(nèi)獲取鎖,若超過設(shè)定時(shí)間仍無法獲取,則不再等待并繼續(xù)執(zhí)行后續(xù)邏輯。
顯示鎖相比內(nèi)置鎖的優(yōu)勢:
- 可以嘗試非阻塞地獲取鎖,比如 tryLock() 和 tryLock(long timeout, TimeUnit unit) 方法。
- 支持中斷,線程在等待鎖時(shí)可以響應(yīng)中斷請求,這對于那些需要取消長時(shí)間等待的任務(wù)非常有用。
- 可以實(shí)現(xiàn)公平鎖策略,即按照線程請求鎖的順序來分配鎖,避免“饑餓”現(xiàn)象。
- 提供了鎖的監(jiān)聽和喚醒機(jī)制,可通過 Condition 對象實(shí)現(xiàn)更復(fù)雜的同步結(jié)構(gòu)
四、讀寫鎖(Read-Write Locks)
Java的讀寫鎖(Read-Write Locks)是一種特殊的鎖機(jī)制,它允許多個(gè)讀取者同時(shí)訪問共享資源,但在寫入者訪問時(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 對象,并從中提取出讀鎖 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ù),模擬了讀多寫少的場景,這時(shí)讀寫鎖可以有效提高系統(tǒng)并發(fā)性能。
五、條件變量(Condition Objects)
在Java中,條件變量是通過java.util.concurrent.locks.Condition接口實(shí)現(xiàn)的,它與鎖(如ReentrantLock)一起使用,允許線程等待滿足特定條件時(shí)被喚醒。下面是一個(gè)使用Condition對象的示例代碼及詳細(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 對象和從該鎖對象創(chuàng)建的一個(gè) Condition 對象。
- 在 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)者線程檢測到 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() 來喚醒所有等待該條件的線程。
- 注意,每個(gè)方法都在 try-finally 結(jié)構(gòu)中管理鎖的加鎖和解鎖操作,確保即使在異常情況下也能正確釋放鎖。
六、樂觀鎖(Optimistic Locking)
樂觀鎖在Java中的實(shí)現(xiàn)通常依賴于原子變量類(如java.util.concurrent.atomic包下的類)或數(shù)據(jù)庫事務(wù)中的版本號(hào)機(jī)制。樂觀鎖的假設(shè)是大多數(shù)情況下數(shù)據(jù)不會(huì)發(fā)生沖突,因此在修改數(shù)據(jù)前并不加鎖,而是在更新時(shí)檢查在此期間是否有其他線程修改過該數(shù)據(jù)。如果發(fā)現(xiàn)數(shù)據(jù)未被修改,則執(zhí)行更新操作;否則則需要重新讀取、驗(yàn)證并嘗試更新。
以下是一個(gè)使用AtomicInteger作為樂觀鎖機(jī)制實(shí)現(xià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è)可能過時(shí)的值
currentValue = counter.get();
// 檢查在此期間是否已經(jīng)被其他線程修改過
} 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來模擬一個(gè)計(jì)數(shù)器,它具有原子性的get()和compareAndSet()方法。
- compareAndSet()方法(也稱為CAS操作)嘗試將當(dāng)前值與預(yù)期值進(jìn)行比較,如果當(dāng)前值等于預(yù)期值,則以原子方式更新為新值。這里的預(yù)期值就是我們在循環(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)境下,樂觀鎖通過不斷的重試確保了最終只有一個(gè)線程成功地進(jìn)行了原子性更新,從而實(shí)現(xiàn)了線程安全的計(jì)數(shù)操作。
七、悲觀鎖(Pessimistic Locking)
悲觀鎖在Java中通常表現(xiàn)為獲取到一個(gè)鎖后,其他線程嘗試訪問該資源時(shí)會(huì)立即被阻塞,直到持有鎖的線程釋放鎖。最直接的例子就是使用synchronized關(guān)鍵字修飾的方法或代碼塊。但為了更好地說明數(shù)據(jù)庫層面的悲觀鎖實(shí)現(xiàn),這里提供一個(gè)基于JDBC和Hibernate的示例:
JDBC示例(通過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來獲取悲觀鎖
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示例(通過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í)體并顯式地請求悲觀鎖
MyEntity entity = session.get(MyEntity.class, id, LockMode.PESSIMISTIC_WRITE);
// 假設(shè)我們在這里修改了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示例中,通過設(shè)置事務(wù)并執(zhí)行SELECT ... FOR UPDATE語句,在讀取記錄的同時(shí)獲得了對該記錄的悲觀鎖,使得其他事務(wù)無法修改這條記錄,直至當(dāng)前事務(wù)結(jié)束。
- 在Hibernate示例中,通過session.get()方法加載實(shí)體對象時(shí)指定了LockMode.PESSIMISTIC_WRITE模式,這樣在獲取實(shí)體時(shí)即獲得了一把悲觀寫鎖,阻止其他事務(wù)對同一實(shí)體進(jìn)行并發(fā)修改。
八、自旋鎖(Spin Locks)
自旋鎖在Java中主要用于解決線程間短時(shí)間的同步問題,尤其適用于等待時(shí)間極短并且CPU資源相對充足的場景。在Java中并沒有直接提供名為“自旋鎖”的API,但是可以通過循環(huán)和volatile關(guān)鍵字模擬實(shí)現(xiàn)自旋鎖的行為。以下是一個(gè)簡單的自旋鎖示例:
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等工具類提供的原子操作來實(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()方法通過不斷地檢查并試圖更新isLocked變量來實(shí)現(xiàn)自旋。當(dāng)isLocked為false時(shí),使用compareAndSet()原子操作將其設(shè)置為true,如果成功則說明當(dāng)前線程獲得了鎖,并跳出循環(huán)。
- unlock()方法將isLocked設(shè)置為false,以釋放鎖。
- compareAndSet()方法利用Java的原子性操作(這里假設(shè)使用了AtomicBoolean)確保對isLocked變量的修改是原子性的,這可以避免多線程環(huán)境下數(shù)據(jù)競爭的問題。
九、StampedLock
Java中的StampedLock是Java 8引入的一個(gè)高性能的并發(fā)工具類,它提供了更靈活的讀寫鎖機(jī)制,包括悲觀讀鎖、樂觀讀鎖和寫鎖。每個(gè)鎖操作都會(huì)返回一個(gè)戳記(stamp),后續(xù)的操作可以通過這個(gè)戳記來驗(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ù)是否被其他線程修改過
// 如果沒有被修改,則可以安全地使用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提供三種模式的鎖:
- 悲觀讀鎖:通過readLock()方法獲取,類似于ReentrantReadWriteLock中的讀鎖,當(dāng)有寫鎖持有時(shí),讀鎖會(huì)阻塞。
- 樂觀讀鎖:通過tryOptimisticRead()方法嘗試獲取,該方法立即返回,不會(huì)阻塞,但必須在之后調(diào)用validate(long stamp)方法確認(rèn)讀取期間是否有寫鎖發(fā)生改變,如果數(shù)據(jù)被修改則需升級(jí)為悲觀讀鎖。
- 寫鎖:通過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ā)場景,權(quán)衡鎖的開銷與安全性,合理選擇并應(yīng)用合適的鎖實(shí)現(xiàn)。
到此這篇關(guān)于Java鎖機(jī)制詳細(xì)講解與實(shí)戰(zhàn)應(yīng)用的文章就介紹到這了,更多相關(guān)Java鎖機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深度對比與解析SpringBoot中的application.properties與application.yml
在Springboot項(xiàng)目中,使用.properties和.yml配置是等效的,均可以正常識(shí)別并使用,本文將為大家深入對比與解析一下二者的使用與區(qū)別,希望對大家有一定的幫助2025-04-04
java數(shù)據(jù)結(jié)構(gòu)之樹基本概念解析及代碼示例
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)之樹基本概念解析及代碼示例,介紹了樹的定義,基本術(shù)語,主要操作及實(shí)現(xiàn)等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可了解下。2017-11-11
MapStruct實(shí)體轉(zhuǎn)換及List轉(zhuǎn)換的方法講解
今天小編就為大家分享一篇關(guān)于MapStruct實(shí)體轉(zhuǎn)換及List轉(zhuǎn)換的方法講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03
swing中Tree與滾動(dòng)條用法實(shí)例分析
這篇文章主要介紹了swing中Tree與滾動(dòng)條用法,以實(shí)例形式分析了java基于swing實(shí)現(xiàn)圖形界面的使用技巧,需要的朋友可以參考下2015-09-09
Java Web 登錄頁面的實(shí)現(xiàn)代碼實(shí)例
這篇文章主要介紹了Java Web 登錄頁面的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
通過實(shí)例學(xué)習(xí)Either 樹和模式匹配
這篇文章主要介紹了通過實(shí)例學(xué)習(xí)Either 樹和模式匹配,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06

