Java併發之Semaphore的源碼分析


Semaphore是一個計數信號量,採用的是共享鎖的方式來控制。

主要對下面2個方法進行分析:

acquire(int)來獲取信號量,直到只有一個可以用或者出現中斷。

release(int)用來釋放信號量,將信號量數量返回給Semaphore

public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
}


AQS:acquireSharedInterruptibly

public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0) //小於0表示沒有獲得共享鎖
            doAcquireSharedInterruptibly(arg);
    }


tryAcquireShared(arg)

protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }


nonfairTryAcquireShared(acquires)

final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();//獲取去中的信號量數
                int remaining = available - acquires;//剩餘信號量數
                //1.信號量數大於0,獲取共享鎖,並設置執行compareAndSetState(available, remaining),返回剩餘信號量數
                //2.信號量數小於等於0,直接返回負數
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }


變量state的源碼,採用了volatile的可見修飾

    /**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a <tt>volatile</tt> read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }


doAcquireSharedInterruptibly(arg)

//沒有獲得共享鎖
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //加入等待隊列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                //前面的節點等於頭結點
                if (p == head) {
                	//嘗試獲取共享鎖
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                    	//將當前節點設置爲head
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 當前線程一直等待,直到獲取到共享鎖。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

釋放信號量release(int)的源碼

public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }


releaseShared(int arg)

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }


tryReleaseShared(arg)

protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();//獲取當前的信號量數
                int next = current + releases;//將釋放的信號量還給Semaphore
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))//設置信號量
                    return true;
            }
        }


doReleaseShared()

private void doReleaseShared() {
    for (;;) {
        // 獲取CLH隊列的頭節點
        Node h = head;
        // 如果頭節點不爲null,並且頭節點不等於tail節點。
        if (h != null && h != tail) {
            // 獲取頭節點對應的線程的狀態
            int ws = h.waitStatus;
            // 如果頭節點對應的線程是SIGNAL狀態,則意味着“頭節點的下一個節點所對應的線程”需要被unpark喚醒。
            if (ws == Node.SIGNAL) {
                // 設置“頭節點對應的線程狀態”爲空狀態。失敗的話,則繼續循環。
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;
                // 喚醒“頭節點的下一個節點所對應的線程”。
                unparkSuccessor(h);
            }
            // 如果頭節點對應的線程是空狀態,則設置“文件點對應的線程所擁有的共享鎖”爲其它線程獲取鎖的空狀態。
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 如果頭節點發生變化,則繼續循環。否則,退出循環。
        if (h == head)                   // loop if head changed
            break;
    }
}


一個簡單實例:

package thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

class SDTask extends Thread
{
	
	private Semaphore s;
	
	public SDTask(Semaphore s,String name)
	{
		super(name);
		this.s = s;
	}
	public void run()
	{
		try
		{
			System.out.println(Thread.currentThread().getName()+" 嘗試獲取3個信號!!!");
			s.acquire(3);
			System.out.println(Thread.currentThread().getName()+" 獲取了3個信號!!!");
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}finally
		{
			System.out.println(Thread.currentThread().getName()+" 釋放了3個信號!!!");
			s.release(3);
		}
	}
}
public class SemaphoreDemo2
{
	public static void main(String[] args)
	{
		Semaphore s = new Semaphore(7);
		for(int i=0; i<3; i++)
		{
			new SDTask(s,"thread"+i).start();;
		}
	}
}

結果:

thread0 嘗試獲取3個信號!!!
thread2 嘗試獲取3個信號!!!
thread0 獲取了3個信號!!!
thread2 獲取了3個信號!!!
thread1 嘗試獲取3個信號!!!
thread0 釋放了3個信號!!!
thread2 釋放了3個信號!!!
thread1 獲取了3個信號!!!
thread1 釋放了3個信號!!!


總結:

Semaphore並沒有採用Lock進行鎖操作,使用volatile state和for循環,通過修改條件來改變線程的操作。










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章