Lock接口

Lock與synchronized

  Lock和synchronized在功能上是同樣的。不過Lock提供了一些其餘功能,包括定時的鎖等待、可中斷的鎖等待、公平性,以及實現非塊結構的加鎖html

從性能上Lock的實現類ReentrantLock在JDK5.0以前要好於synchronized,在JDK6.0以後,synchronized作了優化,因此二者的性能相差無幾了。java

那在使用上應該選擇哪一個呢?在《Java併發編程實戰》中有一句話:"僅當內置鎖不能知足需求時,才能夠考慮使用ReentrantLock"。在一些內置鎖沒法
git

知足需求的狀況下,ReentrantLock能夠做爲一種高級工具。當須要一些高級功能時才應該使用ReentrantLock,這些功能包括:可定時的、可輪詢的與可
編程

中斷的鎖獲取操做,公平隊列,以及非塊結構的鎖。不然,仍是應該優先使用synchronized。安全

  在JDK5.0中,內置鎖與ReentrantLock相比還有另一個優勢:在線程轉儲中能給出在哪些調用幀中得到了哪些鎖,並可以檢測出發生死鎖的線程。JVM併發

並不知道哪些線程持有ReentrantLock,所以在調試使用ReentrantLock的線程問題時,將起不到幫助做用。JDK6.0解決了這個問題,它提供了一個管理和調試app

接口,鎖能夠經過該接口進行註冊,從而與ReentrantLock相關的加鎖信息就能出如今線程轉儲中,並經過其餘的管理接口和調試接口來訪問。ide

  在內置鎖中,死鎖是一個很嚴重的問題,恢復程序惟一的方法是從新啓動程序,而防止死鎖的惟一方法就是在構造程序時避免出現不一致的鎖順序。函數

ReentrantLock有可定時與可輪詢的功能,這就從另外一個方面避免了死鎖的發生(注意,使用ReentrantLock必定要在finally中釋放鎖)。高併發

Lock的實現類ReentrantLock(重入鎖)

  重入鎖ReentrantLock,顧名思義,就是支持重進入的鎖(synchronized隱式的支持重進入),它表示該鎖可以支持一個線程對資源的重複加鎖。除此以外,

該鎖還支持獲取鎖時的公平和非公平性選擇。

  一、ReentrantLock的基本使用。

package org.burning.sport.javase.thread.reentrantlock;

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

public class ReentrantLockTest implements Runnable{
    private static Lock lock = new ReentrantLock();
    private int i;

    @Override
    public void run() {
        while(true) {
            increment();
        }
    }

    public void increment() {
        try {
            lock.lock();
            i++;
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            //必定要在finally這裏解鎖,不然就是定時炸彈
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockTest test = new ReentrantLockTest();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        Thread t3 = new Thread(test);

        t1.setName("線程一:");
        t2.setName("線程二:");
        t3.setName("線程三:");

        t1.start();
        t2.start();
        t3.start();
    }
}

   二、鎖的公平性

    若是在絕對時間上,先對鎖進行獲取的請求必定先被知足,那麼這個鎖是公平的,反之,是不公平的。ReentrantLock提供了一個構造函數,可以控制鎖是不是公平的。

  public ReentrantLock(boolean fair); 默認是非公平的。公平鎖保證了鎖的獲取按照FIFO原則,而代價是進行大量的線程切換影響性能,非公平的鎖則會形成線程的 「飢餓」。

  三、Lock的中斷響應

    線程的中斷 中能夠看到,Lock是能夠響應線程觸發的中斷的。只要在獲取鎖的時候用 lock.lockInterruptibly();就能夠了。

  四、輪詢鎖與定時鎖

    lock.tryLock();  和 lock.tryLock(5, TimeUtil.SECONDS);  在內置鎖中,死鎖是一個很嚴重的問題,恢復程序惟一的方法是從新啓動程序,而防止死鎖的惟一方法就是在

  構造程序時避免出現不一致的鎖順序。ReentrantLock有可定時與可輪詢的功能,這就從另外一個方面避免了死鎖的發生(注意,使用ReentrantLock必定要在finally中釋放鎖)。 

public class TryLockTest implements Runnable {
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                lock.lock();
                //do something .....
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

   五、ReentrantLock的好搭檔Condition條件

    Condition定義了等待/通知兩種類型的方法,當前線程調用這些方法時,須要提早獲取到Condition對象關聯的鎖。Condition對象是由Lock對象(調用Lock對象的new Condition()方法)

  建立出來的,換句話說,Condition是依賴Lock對象的。

  示例代碼:

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

public class ConditionUseCase {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void conditionWait() throws InterruptedException {
        lock.lock();
        try {
            condition.await();
        } finally {
            lock.unlock();
        }
    }

    public void conditionSignal() throws InterruptedException {
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}

   Condition的(部分)方法及描述:

  void await() throws InterruptedException   

  當前線程進入等待狀態直到被通知(signal)或中斷,當前線程將進入運行狀態且從await()方法返回的狀況,包括:

  其餘線程調用該Condition的signal()或signalAll()方法,而當前線程被選中喚醒

     □ 其餘線程(調用interrupt()方法)中斷當前線程;

  □ 若是當前等待線程從await()方法返回,那麼代表該線程已經獲取了Condition對象所對應的鎖;

       void awaitUninterruptibly() 

  當前線程進入等待狀態直到被通知,從方法名稱上能夠看出該方法對中斷不敏感

  long awaitNanos(long nanosTimeout) throws InterruptedException 

  當前線程進入等待狀態直到被通知、中斷或者超時。返回值表示剩餘的時間,若是在nanosTimeout納秒

    以前被喚醒,那麼返回值就是(nanosTimeout - 實際耗時) ,若是返回值是0或者負數,那麼能夠認定已經超時了

  boolean awaitUntil(Date deadline) throws InterruptedException 

  當前線程進入等待狀態直到被通知、中斷或者到某個時間。若是沒有到指定時間就被通知,方法返回true,不然,

  表示到了指定時間,方法返回false

    void signal()  喚醒一個等待在Condition上的線程,該線程從等待方法返回前必須得到與Condition相關聯的鎖

  void signalAll()  喚醒全部等待在Condition上的線程,可以從等待方法返回的線程必須得到與Condition相關聯的鎖。

   示例代碼:

  https://gitee.com/play-happy/base-project/blob/developer/src/main/java/org/burning/sport/javase/thread/reentrantlock/condition/WaxOmatic.java

線程阻塞工具類:LockSupport

  LockSupport是一個很是方便實用的線程阻塞工具,它能夠在線程內任意位置讓線程阻塞。和Thread.suspend()相比,它彌補了因爲resume()在前發生,

  致使線程沒法記錄執行的狀況。和Object.wait()相比,它不須要先得到某個對象的鎖,也不會拋出InterruptedException異常。

  LockSupport提供的阻塞和喚醒的方法

  void park()   阻塞當前線程,若是調用 unpark(Thread thread) 方法或者當前線程被中斷,才能從park方法返回

  void parkNanos(long nanos)  阻塞當前線程,最長不超過nonos納秒,返回條件在park()的基礎上增長了超時返回

  void parkUntil(long deadline)  阻塞當前線程,知道deadline時間(從1970年開始到deadline時間的毫秒數)

  void unpark(Thead thread)  喚醒處於阻塞狀態的線程Thread

  LockSupport sample demo:

package org.burning.sport.javase.thread.reentrantlock.lock.support;

import java.util.concurrent.locks.LockSupport;

public class LockSupportTest {
    private static Object u = new Object();
    static ChangeObjectThread thread1 = new ChangeObjectThread("t1");
    static ChangeObjectThread thread2 = new ChangeObjectThread("t2");
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String threadName) {
            super.setName(threadName);
        }

        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        thread1.start();
        Thread.sleep(100);
        thread2.start();
        LockSupport.unpark(thread1);
        LockSupport.unpark(thread2);
        thread1.join();
        thread2.join();

    }
}

讀寫鎖 ReentrantReadWriteLock

  一、使用示例:

package org.burning.sport.javase.thread.readwritelock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Cache {
    static Map<String, String> map = new HashMap<>();
    static ReadWriteLock lock = new ReentrantReadWriteLock();
    static Lock readLock = lock.readLock();
    static Lock writeLock = lock.writeLock();

    public static final Object get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public static final void put(String key, String value) {
        writeLock.lock();
        try {
            map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    public static void clear() {
        writeLock.lock();
        try {
            map.clear();
        } finally {
            writeLock.unlock();
        }
    }
}

   二、讀寫鎖的實現分析:

  2.一、讀寫狀態設計

    讀寫鎖一樣依賴自定義同步器來實現同步功能。而讀寫狀態就是其同步器的同步狀態。同步狀態表示鎖被一個線程重複獲取的次數,而讀寫鎖的自定義同步器須要在

  同步狀態(一個整形變量)上維護多個讀線程和一個寫線程的狀態。若是在一個整形變量上維護多種狀態,就必定須要 「按位切割使用」 這個變量,讀寫鎖將變量切分爲了兩

  個部分,高16位表示讀,低16位表示寫。

  2.二、寫鎖的獲取與釋放

    寫鎖是一個支持重入的排它鎖。若是當前線程已經獲取了寫鎖,則增長寫狀態。寫鎖的釋放與ReentrantLock的釋放過程基本相似,每次釋放均減小寫狀態,當寫狀態

  爲0時表示寫鎖已被釋放。

  2.三、讀鎖的獲取與釋放

    讀鎖是一個支持重入的共享鎖,它可以被多個線程同時獲取,在沒有其餘寫線程訪問(或者寫狀態爲0)時,讀鎖總會被成功的獲取,而所作的也只(線程安全的)是增長讀

  狀態。讀鎖的每次釋放均減小讀狀態,減小的值是(1<<16)。

  2.四、鎖降級

    鎖降級指的是寫鎖降級成爲讀鎖。若是當前線程擁有寫鎖,而後將其釋放,最後再獲取讀鎖,這種分段完成的過程不能稱之爲鎖降級。鎖降級是指把持住(當前擁有)寫鎖,

  再獲取到讀鎖,隨後釋放寫鎖的過程。(ReentrantReadWriteLock不支持鎖升級)

參考:

  【1】《Java併發編程的藝術》,方騰飛

  【2】《Java併發編程實戰》,童雲蘭

  【3】《Java高併發程序設計》,葛一鳴

  【4】《Think In Java》,第21章 併發

相關文章
相關標籤/搜索