歡迎來到Java鎖定示例教程。通常在處理多線程環境時,我們使用
Java Lock
大多數情況下,synchronized關鍵字是首選,但它存在一些缺點,這導致了Java Concurrency套件中Lock API的加入。Java 1.5 Concurrency API帶來了
java.util.concurrent.locks
套件,其中包含Lock
接口和一些實現類,以改進對象鎖定機制。Java Lock API中的一些重要接口和類包括:
-
Lock: 這是Lock API的基本接口。它提供了synchronized關鍵字的所有功能,並提供了其他創建不同鎖定條件、為線程提供等待鎖的超時等方式。一些重要方法包括lock()以獲取鎖、unlock()以釋放鎖、tryLock()以在一定時間內等待鎖、newCondition()以創建條件等。
-
條件:條件物件類似於 物件等待-通知 模型,具有創建不同等待集的附加功能。條件物件始終由鎖物件創建。一些重要的方法包括 await(),類似於 wait(),以及 signal()、signalAll(),類似於 notify() 和 notifyAll() 方法。
-
讀寫鎖:它包含一對相關的鎖,一個用於只讀操作,另一個用於寫入。只要沒有寫入線程,讀取鎖就可以同時由多個讀取器線程持有。寫入鎖是獨占的。
-
可重入鎖:這是Lock接口的最常用實現類。此類以與synchronized關鍵字類似的方式實現Lock接口。除了Lock接口實現之外,可重入鎖還包含一些實用方法,用於獲取持有鎖的線程、等待獲取鎖的線程等。同步塊具有可重入的特性,即如果一個線程在監視器對象上持有鎖,並且另一個同步塊需要在同一監視器對象上持有鎖,則線程可以進入該代碼塊。我認為這就是類名稱為可重入鎖的原因。讓我們通過一個簡單的例子來理解這個特性。
public class Test{ public synchronized foo(){ //do something bar(); } public synchronized bar(){ //do some more } }
如果一個線程進入foo(),它會鎖定Test對象,因此當它嘗試執行bar()方法時,線程可以執行bar()方法,因為它已經持有Test對象的鎖,即與synchronized(this)相同。
Java Lock示例 – Java中的ReentrantLock
現在讓我們看一個簡單的示例,在該示例中,我們將使用Java Lock API替換`synchronized`關鍵字。假設我們有一個Resource類別,其中包含一些需要線程安全的操作以及一些不需要線程安全的方法。
package com.journaldev.threads.lock;
public class Resource {
public void doSomething(){
//進行一些操作,如數據庫讀寫等
}
public void doLogging(){
//日誌記錄,不需要線程安全
}
}
現在假設我們有一個Runnable類,在這個類中我們將使用資源方法。
package com.journaldev.threads.lock;
public class SynchronizedLockExample implements Runnable{
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
請注意,我正在使用同步塊來獲取對資源物件的鎖。我們本可以在類中創建一個虛擬物件,並將其用於鎖定目的。現在讓我們看看如何使用Java Lock API並在不使用同步關鍵字的情況下重寫上述程序。我們將在Java中使用ReentrantLock。
package com.journaldev.threads.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//釋放鎖
lock.unlock();
}
resource.doLogging();
}
}
正如您所看到的,我正在使用tryLock()方法來確保我的線程僅等待一段明確的時間,並且如果它無法獲得對象的鎖,則僅會記錄並退出。另一個重要的注意點是使用try-finally塊來確保即使doSomething()方法調用引發任何異常,也會釋放鎖。
Java Lock vs synchronized
根據上述詳細信息和程序,我們可以輕鬆得出Java Lock和同步之間的以下區別。
- Java Lock API提供了更多的可見性和鎖定選項,不像同步,其中一個線程可能會無限期地等待鎖,我們可以使用tryLock()來確保線程僅等待特定的時間。
- 同步代碼更加清潔且易於維護,而使用Lock,我們被迫使用try-finally塊來確保即使在lock()和unlock()方法調用之間引發異常,也會釋放鎖。
- 同步块或方法只能覆盖一个方法,而我们可以在一个方法中获取锁,然后在另一个方法中使用 Lock API 释放它。
- 同步关键字不提供公平性,而我们可以在创建 ReentrantLock 对象时将公平性设置为 true,以便等待时间最长的线程首先获得锁。
- 我们可以为 Lock 创建不同的条件,不同的线程可以等待不同的条件。
这就是 Java 锁示例、Java 中的 ReentrantLock,以及与同步关键字的比较分析。
Source:
https://www.digitalocean.com/community/tutorials/java-lock-example-reentrantlock