Java中的鎖----重入鎖

ReentrantLock

重入鎖(ReentrantLock):支持重進入的鎖,他表示該鎖可以支持一個線程對資源的重複加鎖。在調用lock()方法時,已經獲取到鎖的線程,可以再次調用lock()方法獲取鎖而不被阻塞。 公平鎖:在絕對時間上,先對鎖進行獲取的請求必定先被知足,也就是等待時間最長的線程最優先獲取鎖。反之則是不公平鎖。java

1.實現重進入

  1. 線程再次獲取鎖。鎖須要去識別獲取鎖的線程是否爲當前佔據鎖的線程,若是是,則再次成功獲取。緩存

  2. 鎖的最終釋放。線程重複n次獲取了鎖,隨後在第n次釋放該鎖後,其餘線程可以獲取到該鎖。鎖的最終釋放要求鎖對於獲取進行計數自增,計數表示當前鎖被重複獲取的次數,而鎖被釋放時,計數自減,當計數等於0時表示因此經成功釋放。安全

/**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
            非公平性鎖獲取的示例

         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

該方法增長了再次獲取同步狀態的處理邏輯:經過判斷當前線程是否爲獲取鎖的線程來決定獲取操做是否成功,若是是獲取鎖的線程再次請求,則將同步狀態值進行增長並返回true,表示獲取同步狀態成功。 成功獲取鎖的線程再次獲取鎖,只是增長了同步狀態值,這也就要求ReentrantLock在釋放同步狀態時減小同步狀態值。多線程

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

公平與非公平獲取鎖的區別

/**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

tryAcquire 是公平鎖的獲取,同 nonfairTryAcquire 相比,多了判斷條件:hasQueuedPredecessors();既加入了同步隊列中當前節點是否有前驅節點的判斷,若是該方法返回true,則表示有線程比當前線程更早地請求獲取鎖,所以須要等待前驅線程獲取並釋放以後才能繼續獲取鎖。併發

讀寫鎖

讀寫鎖:在同一時刻能夠容許多個多線程訪問,可是在寫線程訪問時,全部的讀線程和其餘寫線程均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,經過分離讀鎖和寫鎖,使得併發性相比通常的排他鎖有了很大的提示。app

Java併發包提供讀寫鎖的實現是ReentrantReadWriteLock();特性:公平性選擇,重進入,鎖降級。less

讀寫鎖的接口與示例

ReadWriteLock 僅定義了 獲取讀鎖和寫鎖的兩個方法,既 readLock()方法 和 WriteLock() 方法,而其實現---ReentranReadWriteLock,除了實現接口方法,還提供了一些便於外界監控其內部狀態的方法。oop

package com.lock;

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

/**
 * Created by cxx on 2018/1/17.
 * 緩存示例說明讀寫鎖的實現方式
 */
public class Cache {
    
    static Map<String ,Object> map = new HashMap<String,Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();
    
    //獲取一個key對應的value
    public static final Object get(String key){
        r.lock();
        try {
            return map.get(key);
            
        }finally {
            r.unlock();
        }
    }
    
    // 設置key對應的value,並返回舊的value
    public static final Object put(String key ,Object value){
        w.lock();
        try {
            return map.put(key,value);
        } finally {
            w.unlock();
        }
    }
    
    //狀況全部的內容
    public static final void clear(){
        w.lock();
        
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }
    
}

Cache組合一個非線程安全的HashMap做爲緩存的實現,同時使用讀寫鎖的讀鎖和寫鎖來保證Cache是線程安全的。在操做get() 方法中,須要獲取讀鎖,這使得併發訪問該方法時不會被阻塞。寫操做put 方法和clear方法,在更新HashMap是必須提早獲取寫鎖,當獲取寫鎖後,其餘線程對於讀鎖和寫鎖的獲取均被阻塞,而只有寫鎖被釋放以後,其餘讀寫操做才能繼續。post

讀寫鎖的實現方式

ReentrantReadWriteLock 的實現,主要包括:讀寫狀態的設計,寫鎖的獲取與釋放、讀鎖的獲取與釋放以及鎖降級。ui

讀寫狀態的設計

讀寫鎖一樣依賴自定義同步器實現同步功能,而讀寫狀態就是其同步器的同步狀態。同步狀態表示鎖被一個線程重複獲取的次數,而讀寫鎖的自定義同步器須要在同步狀態上維護多個讀線程和一個寫線程的狀態,使得該狀態的設計成爲讀寫鎖實現的關鍵。

若是在一個整型變量上維護多種狀態,須要」按位切割使用「這個變量。高16位表示讀,低16位表示寫。

結論:同步狀態值S,S不等於0時,當寫狀態(S & 0x0000FFFF) 等於 0 時,則讀狀態(s >> 16) 大於 0,既讀鎖已被獲取。

寫鎖的獲取與釋放

寫鎖是一個支持重進入的排他鎖。若是當前線程已經獲取了寫鎖,則增長寫狀態。若是當前線程在獲取寫鎖時,讀鎖已經被獲取(讀狀態不爲0)或者該線程不是已經獲取寫鎖的線程,則當前線程進入等待狀態。

protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

除了重入條件的判斷以外,增長了一個讀鎖是否存在的判斷。若是存在讀鎖,則寫鎖不能被獲取。只有等待其餘讀線程都釋放了讀鎖,寫鎖才能被當前線程獲取,而寫鎖一旦被獲取,則其餘讀寫線程的後續訪問均被阻塞。

讀鎖的獲取與釋放

讀鎖是一個支持重進入的共享鎖,他可以被多個線程同時獲取,在沒有其餘寫線程訪問(或者寫狀態爲0)時,讀鎖總會被成功地獲取,而所作的也只是(線程安全的)增長讀狀態。讀狀態時全部線程獲取讀鎖次數的總和,而每一個線程各自獲取讀鎖的次數只能選擇保存在ThreadLocal中,由線程自身維護,這使獲取讀鎖的實現變得複雜。

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }

鎖降級

鎖降級指的是寫鎖降級成爲讀鎖。若是當前線程用於寫鎖,而後將其釋放,最後在獲取讀鎖,這種分段完成的過程不能稱之爲鎖降級。鎖降級是指把持住寫鎖,在獲取到讀鎖,隨後釋放寫鎖的過程。

public void processData(){
        readLock.lock();
        if (!update) {
            //必須先釋放讀鎖
            readLock.unlock();
            //鎖降級從寫鎖獲取到開始
            writeLock.lock();
            try {
                if (!update){
                    //準備數據的流程(略)
                    update = true;
                }
                readLock.lock;
            }finally {
                writeLock.unlock();
            }
            //鎖降級完成,寫鎖降級爲讀鎖
        }
        
        try {
            //使用數據的流程
        }finally {
            readLock.unLock();
        }
    }

鎖降級的必要性:主要是爲了保證數據的可見性,若是當前線程不獲取讀鎖而是直接釋放寫鎖,假設此刻另外一個線程獲取了寫鎖並修改了數據,那麼當前線程沒法感知線程T的數據更新。若是當前線程獲取讀鎖,既遵循鎖降級的步驟,則線程T將會被阻塞,直到當前線程使用數據並釋放讀鎖以後,線程T才能獲取寫鎖進行數據更新。

RentrantReadWriteLock不支持所升級,目的也是保證數據可見性,若是讀鎖已被多個線程獲取,其中任意線程成功獲取了寫鎖並更新了數據,則其更新對其餘獲取到讀鎖的線程是不可見的。

相關文章
相關標籤/搜索