天天看點

線程---重入鎖(ReentrantLock)

重入鎖(ReentrantLock)

 鎖是用來控制多個線程通路共享資源的方式。

 一般來說,一個鎖能夠防止多個線程同時通路共享資源(但是有些鎖可以允許多個線程并發的通路共享資源,比如讀寫鎖)。在Lock接口出現之前,Java程式是靠synchronized關鍵字實作鎖功能的,而Java SE 5之後,并發包中新增了Lock接口(以及相關實作類)用來實作鎖功能,它提供了與synchronized關鍵字類似的同步功能,隻是在使用時需要顯式地擷取和釋放鎖。雖然它缺少了(通過synchronized塊或者方法所提供的)隐式擷取釋放鎖的便捷性,但是卻擁有了鎖擷取與釋放的可操作性、可中斷的擷取鎖以及逾時擷取鎖等多種synchronized關鍵字所不具備的同步特性。

  • 重入鎖:

    支援重進入的鎖,它表示該鎖能夠支援一個線程對資源的重複加鎖。除此之外,該鎖還支援擷取鎖時的公平和非公平性選擇。

  • 源碼探究:
public class ReentrantLock implements Lock, java.io.Serializable
           
  • 方法:
void lock():加鎖
void lockInterruptibly() throws InterruptedException:可中斷鎖
boolean tryLock():嘗試性的進行加鎖
boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException: 在有限時間内進行嘗試性加鎖
void unlock():釋放鎖
Condition newCondition():通信類

           
  • 構造函數:
無參構造,預設采用非公平性鎖
public ReentrantLock() {
        sync = new NonfairSync();
    }
    
有參構造,參數為Boolean,true:公平性鎖,false:非公平性鎖
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

           

AbstractQueuedSynchronizer

基于FIFO隊列,可以用于建構鎖或者其他相關同步裝置的基礎架構。

該同步器利用了一個int來表示狀态。

同步器的開始提到了其實作依賴于一個FIFO隊列,

那麼隊列中的元素Node就是儲存着線程引用和線程狀态的容器,

每個線程對同步器的通路,都可以看做是隊列中的一個節點。

Node的主要包含以下成員變量:

Node {
	int waitStatus;//節點的狀态
    Node prev;//前驅節點
    Node next;//後繼節點
    Node nextWaiter;//存儲condition隊列中的後繼節點
    Thread thread;//入隊列時的目前線程
}

表示節點的狀态。其中包含的狀态有:
CANCELLED,值為1,表示目前的線程被取消;
SIGNAL,值為-1,表示目前節點的後繼節點包含的線程需要運作,也就是unpark;
CONDITION,值為-2,表示目前節點在等待condition,也就是在condition隊列中;
PROPAGATE,值為-3,表示目前場景下後續的acquireShared能夠得以執行;
值為0,表示目前節點在sync隊列中,等待着擷取鎖。
           

AQS(AbstractQueuedSychronizer—-Node組成的連結清單)中的state:

state=0:表示鎖是空閑狀态;

state>0:表示鎖被占用;

state<0:表示溢出;

state > 1 : 目前擷取鎖的次數。

重入鎖的實作:

目前線程每擷取一次鎖就進行+1操作,每次釋放鎖對state減一,當state=0時才是真正的釋放鎖。

  • ReentrantLock方法列舉:
ReentrantLock方法列舉:

int getHoldCount():傳回目前鎖擷取的次數
boolean isHeldByCurrentThread():
boolean isLocked()
Thread getOwner()
boolean hasQueuedThreads()
boolean hasQueuedThread(Thread thread):判斷傳入的線程是否處于阻塞對列中
int getQueueLength()
Collection<Thread> getQueuedThreads()
boolean hasWaiters(Condition condition)
int getWaitQueueLength(Condition condition)
    
    ReentrantLock底層實作有三個内部類:FairSync、NonfairSync、Sync
    
     abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();
擷取鎖相關方法:
        final boolean nonfairTryAcquire(int acquires) {
        //擷取目前線程
            final Thread current = Thread.currentThread();
		//擷取鎖的狀态
            int c = getState();
            if (c == 0) {
				//表示目前鎖空閑,通過CAS擷取鎖,擷取成功則将state置為1 ,并且将目前線程記錄下來,直接傳回
                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);//若沒有到達上限,則直接更新state,直接傳回,加一操作
                return true;
            }
			//目前擷取鎖的線程不是目前線程
            return false;
        }

		//嘗試釋放鎖
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
			//擷取鎖的線程非目前線程,則直接抛出異常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
				//當state減為1時,才真正釋放鎖,将持有鎖的線程置為null
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
		
           
  • 簡單實作方法:
public class ReentrantLock0416 {
    public static void main(String[] args) {
        //ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //讀寫鎖
        ReentrantLock reentrantLock = new ReentrantLock();

       /* *//**預設不公平性鎖
         * Creates an instance of {@code ReentrantLock}.
         * This is equivalent to using {@code ReentrantLock(false)}.
         *//*
        public ReentrantLock() {
            sync = new ReentrantLock.NonfairSync();
        }*/
        ReentrantLock reentrantLock1 = new ReentrantLock(true);//公平性鎖
        reentrantLock.lock();
        System.out.println("重入鎖");
        reentrantLock.unlock();//加鎖更細 顯性實作加鎖

        //線程通信
        Condition condition = reentrantLock.newCondition();
        condition.signal();//類似notify()
        condition.signalAll();//notifyAll()
        try {
            condition.await();//wait()
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            reentrantLock.lockInterruptibly();//中斷加鎖
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

           
  • 公平性鎖和非公平性鎖:

    鎖擷取的公平性問題,如果在絕對時間上,先對鎖進行擷取的請求一定先被滿足,那麼這個鎖是公平的,反之,是不公平的。公平的擷取鎖,也就是等待時間最長的線程最優先擷取鎖,也可以說鎖擷取是順序的。ReentrantLock提供了一個構造函數,能夠控制鎖是否是公平的。

**NonFairSync:**非公平性鎖

第一次通過CAS強制搶鎖,搶鎖失敗則再次嘗試性搶鎖,

嘗試性搶鎖:首先判斷鎖是否被占用(state=0),是則直接CAS來擷取鎖,

擷取成功則修改 state,且将目前線程計入AQS中,成功傳回。

在判斷占用的情況下,占用鎖的線程是否是目前線程,則直接修改state,成功傳回。

以上都不滿足,失敗傳回,若失敗傳回,則目前線程未擷取到鎖,則将線程加入到AQS中。


    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
       
        final void lock() {
            //CAS強制擷取鎖
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //嘗試性搶鎖
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
  //擷取鎖的操作
        final boolean nonfairTryAcquire(int acquires) {
        //擷取目前線程
            final Thread current = Thread.currentThread();
		//擷取鎖的狀态
            int c = getState();
            if (c == 0) {
				//表示目前鎖空閑,通過CAS擷取鎖,擷取成功則将state置為1 ,并且将目前線程記錄下來,直接傳回
                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);//若沒有到達上限,則直接更新state,直接傳回,加一操作
                return true;
            }
			//目前擷取鎖的線程不是目前線程
            return false;
        }



**FairSync:**公平性鎖

嘗試性擷取鎖,若鎖未被占用,判斷目前現場是否滿足條件(AQS隊列為空且目前線程處于AQS隊列的隊頭),修改state,若鎖被占用,且是目前線程占用鎖,修改state,成功傳回。

若以上都不滿足,則失敗傳回。

若未擷取到鎖,則即将線程加入到AQS中的隊列。


    static final class FairSync extends Sync {
        
        final void lock() {
            acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //隊列為空并且目前線程 處于隊列的first的node
                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;
        }
    }
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
		    //隊列不為空
            //且隊列裡隻有一個結點或者頭結點所在的線程不等于目前的線程
    }


           
  • 公平性鎖和不公平性鎖的差別:
  1. 鎖的擷取順序:
  • 公平性與否是針對擷取鎖而言的,如果一個鎖是公平的,那麼鎖的擷取順序就應該符合請求的絕對時間順序,也就是FIFO。
  • 而非公平性鎖擷取鎖是搶占式的,當一個線程擷取鎖後再釋放,有很大的機率會再次擷取到鎖,因為剛釋放鎖的線程會再次擷取到同步狀态的幾率會非常大,使得其他線程隻能在同步隊列中等待。
  1. 主要在Lock()方法實作上不同:
  • 搶鎖次數不同;非公平性鎖進行兩次搶奪,公平性鎖進行一次搶奪;
  • 公平性鎖在搶鎖時判斷是否處于隊頭;