Java鎖定示例 – ReentrantLock

歡迎來到Java鎖定示例教程。通常在處理多線程環境時,我們使用用於線程安全。

Java Lock

大多數情況下,synchronized關鍵字是首選,但它存在一些缺點,這導致了Java Concurrency套件中Lock API的加入。Java 1.5 Concurrency API帶來了java.util.concurrent.locks套件,其中包含Lock接口和一些實現類,以改進對象鎖定機制。Java Lock API中的一些重要接口和類包括:

  1. Lock: 這是Lock API的基本接口。它提供了synchronized關鍵字的所有功能,並提供了其他創建不同鎖定條件、為線程提供等待鎖的超時等方式。一些重要方法包括lock()以獲取鎖、unlock()以釋放鎖、tryLock()以在一定時間內等待鎖、newCondition()以創建條件等。

  2. 條件:條件物件類似於 物件等待-通知 模型,具有創建不同等待集的附加功能。條件物件始終由鎖物件創建。一些重要的方法包括 await(),類似於 wait(),以及 signal()、signalAll(),類似於 notify() 和 notifyAll() 方法。

  3. 讀寫鎖:它包含一對相關的鎖,一個用於只讀操作,另一個用於寫入。只要沒有寫入線程,讀取鎖就可以同時由多個讀取器線程持有。寫入鎖是獨占的。

  4. 可重入鎖:這是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和同步之間的以下區別。

  1. Java Lock API提供了更多的可見性和鎖定選項,不像同步,其中一個線程可能會無限期地等待鎖,我們可以使用tryLock()來確保線程僅等待特定的時間。
  2. 同步代碼更加清潔且易於維護,而使用Lock,我們被迫使用try-finally塊來確保即使在lock()和unlock()方法調用之間引發異常,也會釋放鎖。
  3. 同步块或方法只能覆盖一个方法,而我们可以在一个方法中获取锁,然后在另一个方法中使用 Lock API 释放它。
  4. 同步关键字不提供公平性,而我们可以在创建 ReentrantLock 对象时将公平性设置为 true,以便等待时间最长的线程首先获得锁。
  5. 我们可以为 Lock 创建不同的条件,不同的线程可以等待不同的条件。

这就是 Java 锁示例、Java 中的 ReentrantLock,以及与同步关键字的比较分析。

Source:
https://www.digitalocean.com/community/tutorials/java-lock-example-reentrantlock