【多線程】4.Java中鎖對象Lock的使用

在這裏插入圖片描述

ReentrantLock

除了使用synchronized關鍵字來實現線程之間同步互斥,在JDK1.5中新增了ReentrantLock類也能達到同樣的效果,並且在擴展功能上也更加強大,例如嗅探鎖定、多路分支通知等功能,使用上比synchronized更加靈活。
主要的使用方法:

Lock lock = new ReentrantLock();
try {
	// 獲取鎖
	lock.lock();
	
	doSomething();
} finally {
	// 釋放鎖
	lock.unlock();
}

這裏將synchronized的同步代碼塊的獲取鎖和釋放鎖變成手動控制,更加靈活,但是需要注意鎖沒有釋放的問題,一般在try-catch的語句中可以將unlock放到finally語句塊中,確保鎖一定會釋放。

使用Condition類

與關鍵字synchronized的wait(),notify()/notifyAll()方法類似,ReentrantLock類也提供了Condition類來實現等待/通知模式,它還可以通過多個Condition實現多路通知功能,有選擇性的選擇一部分等待線程進行喚醒。
synchronized就只能通過notify()隨機喚醒一個線程,或者notifyAll()喚醒所有線程,靈活性和效率都不及ReentranLock
具體的用法如下:

Lock lock = new ReentrantLock();
// 新增Condition對象
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();

// 釋放鎖並等待喚醒,只有conditionA的signal()/signalAll()方法能喚醒
conditionA.await(); // 對應Object對象的wait()方法

// 喚醒等待conditionA條件喚醒的線程
conditionA.signal();	// 喚醒一個,對應Object對象的notify()方法
conditionA.signalAll();	// 全部喚醒,對應Object對象的notifyAll()方法

範例中通過多個Condition對象來實現對線程的等待/通知機制進行隔離,線程是等待和發出喚醒都是基於一個Condition條件,不同條件的Condition之間的喚醒相互不影響。
需要注意的是,await()/signal()/signalAll()方法都需要在調用了lock()方法獲取鎖之後才能調用,否則會拋出IllegalMonitorStateException,此條件與Object類的wait()/notify()/notifyAll()相同,都需要在先獲取鎖的場景下調用,假設調用lock()時候鎖被其他線程佔用,這時候則會阻塞線程,等待鎖的釋放。

多生產者與多消費者的假死問題

看下面的代碼片段,這個代碼片段可能導致程序的假死,導致假死的執行順序如下:
經過一段時間後,消費者b, bb都進入等待,生產者aa也是等待狀態,這時候生產者a被喚醒,生產了一個值,調用signal()隨機喚醒一個線程,這時候可能喚醒了生產者aa,生產者aa喚醒後發現hasValue=true則釋放鎖並進入等待狀態,這時候四個線程都是等待狀態,就出現了假死的情況。

package lock.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyService1 {

    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    private boolean hasValue = false;

    public void set() {
        try {
            lock.lock();
            while (hasValue) {
                condition.await();
            }
            System.out.println("打印★");
            hasValue = true;
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void get() {
        try {
            lock.lock();
            while (!hasValue) {
                condition.await();
            }

            System.out.println("打印☆");
            hasValue = false;
            condition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        MyService1 service1 = new MyService1();
        MyThreadA a = new MyThreadA(service1);
        MyThreadA aa = new MyThreadA(service1);
        a.start();
        aa.start();
        MyThreadB b = new MyThreadB(service1);
        MyThreadB bb = new MyThreadB(service1);
        b.start();
        bb.start();
    }

}

class MyThreadA extends Thread {
    private MyService1 myService1;

    MyThreadA(MyService1 myService1) {
        this.myService1 = myService1;
    }

    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService1.set();
        }
    }
}

class MyThreadB extends Thread {
    private MyService1 myService1;

    MyThreadB(MyService1 myService1) {
        this.myService1 = myService1;
    }

    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService1.get();
        }
    }
}

想要解決這個問題,只要將程序中兩個condition.signal();替換成condition.signalAll();即可,這樣保證每次某個線程執行完成之後,其他的各個線程都能得到通知,並且檢查自己是否需要執行操作。
這個做法同樣可以通過多個Condition對象來實現這個效果,代碼片段如下:

package lock.condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyService1 {

    private final Lock lock = new ReentrantLock();
    private final Condition emptyCondition = lock.newCondition();
    private final Condition fullCondition = lock.newCondition();

    private boolean hasValue = false;

    public void set() {
        try {
            lock.lock();
            while (hasValue) {
                emptyCondition.await();
            }
            System.out.println("打印★");
            hasValue = true;
            fullCondition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void get() {
        try {
            lock.lock();
            while (!hasValue) {
                fullCondition.await();
            }

            System.out.println("打印☆");
            hasValue = false;
            emptyCondition.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        MyService1 service1 = new MyService1();
        MyThreadA a = new MyThreadA(service1);
        MyThreadA aa = new MyThreadA(service1);
        a.start();
        aa.start();
        MyThreadB b = new MyThreadB(service1);
        MyThreadB bb = new MyThreadB(service1);
        b.start();
        bb.start();
    }

}

class MyThreadA extends Thread {
    private MyService1 myService1;

    MyThreadA(MyService1 myService1) {
        this.myService1 = myService1;
    }

    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService1.set();
        }
    }
}

class MyThreadB extends Thread {
    private MyService1 myService1;

    MyThreadB(MyService1 myService1) {
        this.myService1 = myService1;
    }

    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            myService1.get();
        }
    }
}

公平鎖和非公平鎖

公平鎖:線程獲取鎖的順序是按照線程加鎖的順序來分配,即先來先到的FIFO先進先出順序。
非公平鎖:當一個線程釋放鎖之後,其他因爲加鎖而阻塞的線程,會使用搶佔機制,等待中的線程隨機獲取得到鎖;非公平鎖可能導致某些線程一直搶不到鎖的問題。
ReentrantLock默認的採用非公平鎖,可以從他的構造器中看到:

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();	// 默認構造器是非公平鎖
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();	// 用戶自定義鎖類型
    }

常用的一些方法

ReentrantLock

  • int getHoldCount()查詢當前線程調用了lock()方法的次數,相當於synchronized()方法的重入次數;
  • int getQueueLength()返回正等待獲取獲取該鎖的線程估計數;
  • int getWaitQueueLength(Condition condition)返回等待與該鎖相關的給定條件Condition的線程估計數;
  • `boolean hasQueuedThread(Thread thread)查詢指定的線程是否正在等待獲取這個鎖;
  • boolean hasWaiters(Condition condition)查詢是否有線程正在等待與該鎖相關的Condition條件(即調用了condition的await()方法);
  • isFair()判斷是不是公平鎖;
  • isHeldByCurrentThread()查詢當前線程是否持有該鎖;
  • boolean isLocked()查詢該鎖是否被某個線程持有(不一定是當前線程);
  • lockInterruptibly()如果當前線程沒有打上中斷標記(thread.interrupt()),則獲取該鎖,否則拋出異常;
  • tryLock()僅在調用時候鎖未被另一個線程持有的情況下,才獲取該鎖,否則不獲取鎖,直接執行下方代碼;
  • tryLock(long timeout, TimeUnit unit)如果鎖在給定等待時間內沒有被另一個線程保持,且當前線程未被中斷,則獲取該鎖。

Condition

  • awaitUninterruptibly()正常來說,當執行condition.await()時候,將線程打上中斷標記,則會拋出異常,該方法相當於忽略中斷標記的await()方法;
  • awaitUntil(long)延時版本的await()方法,可以在等待指定毫秒數後自動喚醒,或者在自動喚醒之前被其他線程喚醒。
    參考:
    jdk8的javadoc文檔

ReentrantReadWriteLock類

ReentrantLock具有完成互斥排他的效果,即統一時間只有一個線程在執行ReentrantLock.lock()方法後面的任務,這樣做效率比較低下。
所以JDK提供了一種讀寫鎖ReentrantReadWriteLock類,可以加快運行效率,分開讀鎖和寫鎖。
讀寫鎖標識也有兩個鎖,一個是讀操作相關的鎖,也叫做共享鎖;另一個是寫操作相關的鎖,也叫做排他鎖。

用法

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

# 讀鎖加鎖、釋放鎖
lock.readLock().lock();
lock.readLock().unlock();
# 寫鎖加鎖
lock.writeLock().lock();
lock.writeLock().unlock();

互斥策略爲:

  • 讀讀共享
  • 讀寫互斥
  • 寫寫互斥
    一個總的原則就是讀的加鎖都是可以多次加鎖,也就是共享的,寫的加鎖是與其他的鎖(讀鎖、寫鎖)都是互斥的,寫鎖需要自己獨佔,寫鎖被某個線程獲取之後,其他線程均不能再對該鎖獲取任何類型的鎖,直到寫鎖釋放。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章