Java Lock示例 - ReentrantLock

引言

在多線程環境下,一般咱們使用 synchronized 關鍵字來保證線程安全。java

大多數狀況下,用 synchronized 關鍵字就足夠了,但它也有一些缺點, 因此在 Java Concurrency 包中引入了 Lock API 。從Java 1.5版開始在 java.util.concurrent.locks 包中提供了處理併發的 Concurrency API 的 Lock 鎖接口和一些實現類來改進 Object 鎖定機制。安全

Java Lock API中的一些重要接口和類

Java Lock API中的一些重要接口和類是:多線程

  • 鎖(Lock):這是Lock API的基本接口。它提供了 synchronized 關鍵字的全部功能,以及爲鎖定建立不一樣條件的其餘方法,爲線程等待鎖定提供超時功能。一些重要的方法是 lock() 獲取鎖,unlock() 釋放鎖,tryLock() 等待鎖定一段時間,newCondition() 建立條件等。
  • 條件(Condition):條件對象相似於對象等待通知( Object wait-notify)模型,具備建立不一樣等待集的附加功能。Condition 對象始終由 Lock 對象建立。一些重要的方法是 await(),相似於Object.wait() 和 signal(),signalAll(),相似於 Object.notify() 和 Object.notifyAll() 方法。
  • 讀寫鎖(ReadWriteLock):它包含一對關聯的鎖,一個用於只讀操做,另外一個用於寫入。只要沒有寫入線程,讀鎖能夠由多個讀取線程同時保持。寫鎖是獨佔的。
  • 重入鎖(ReentrantLock):這是最普遍使用的 Lock 接口實現類。此類以與 synchronized 關鍵字相似的方式實現 Lock 接口。除了 Lock 接口實現以外,ReentrantLock 還包含一些實用程序方法來獲取持有鎖的線程,等待獲取鎖線程等。

synchronized 塊

synchronized 塊本質上是可重入的,即若是一個線程鎖定了監視對象,而且另外一個同步塊須要鎖定在同一個監視對象上,則線程能夠進入該代碼塊。我認爲這就是類名是ReentrantLock的緣由。讓咱們經過一個簡單的例子來理解這個特性。併發

public class Test{

public synchronized foo(){
    //do something
    bar();
  }

  public synchronized bar(){
    //do some more
  }
}

若是一個線程進入 foo(),它就會鎖定Test對象,因此當它嘗試執行 bar() 方法時,容許該線程執行 bar() 方法,由於它已經在 Test 對象上持有鎖,即與 synchronized(this) 效果是同樣的。ide

Java Lock 示例 - Java 中的 ReentrantLock

如今讓咱們看一個簡單的例子,咱們將使用 Java Lock API 替換 synchronized 關鍵字。post

假設咱們有一個 Resource 類,其中包含一些操做,咱們但願它是線程安全的,以及一些不須要線程安全的方法。this

package com.journaldev.threads.lock;

public class Resource {

    public void doSomething(){
        //do some operation, DB read, write etc
    }
    
    public void doLogging(){
        //logging, no need for thread safety
    }
}

如今假設咱們有一個 Runnable 類,咱們將使用 Resource 方法。google

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();
    }
}

請注意,我使用 synchronized 塊來獲取 Resource 對象上的鎖。咱們能夠在類中建立一個虛擬對象,並將其用於鎖定的目的。spa

如今讓咱們看看咱們如何使用 Java Lock API 並重寫上面的程序而不使用 synchronized 關鍵字。咱們將在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{
            //release lock
            lock.unlock();
        }
        resource.doLogging();
    }

}

正如你所看到的,我正在使用 tryLock() 方法來確保個人線程只等待必定的時間,若是它沒有得到對象的鎖定,它只是記錄和退出。另外一個要注意的重點是使用 try-finally 塊來確保即便 doSomething() 方法調用拋出任何異常也會釋放鎖定。

Java Lock 與 synchronized 比較

基於以上細節和程序,咱們能夠很容易地得出 Java Lock 和同步之間的如下差別。

  • Java Lock API 爲鎖定提供了更多的可見性和選項,不像在線程可能最終無限期地等待鎖定的同步,咱們可使用tryLock() 來確保線程僅等待特定時間。
  • 同步關鍵字的代碼更清晰,更易於維護,而使用 Lock,咱們不得不嘗試使用 try-finally 塊來確保即便在 lock() 和 unlock() 方法調用之間拋出異常也會釋放 Lock。
  • 同步塊或方法只能覆蓋一種方法,而咱們能夠在一種方法中獲取鎖,並使用 Lock API 在另外一種方法中釋放它。
  • synchronized 關鍵字不提供公平性,而咱們能夠在建立 ReentrantLock 對象時將公平性設置爲 true,以便最長等待的線程首先得到鎖定。
  • 咱們能夠爲 Lock 建立不一樣的等待條件(Condition),不一樣的線程能夠針對不一樣的條件來 await() 。

這就是 Java Lock 示例,Java 中的 ReentrantLock 以及使用 synchronized 關鍵字的比較分析。

做 者:

關於Pankaj
8087ef00af2610b870ada12392286695?s=120&d=blank&r=g
若是你走得這麼遠,那就意味着你喜歡你正在讀的東西。爲何不直接在Google PlusFacebookTwitter上與我聯繫。我很想直接聽到你對個人文章的想法和意見。

最近我開始建立視頻教程,因此請在Youtube上查看個人視頻。

相關文章
相關標籤/搜索