Java多執行緒12:ReentrantLock中的方法

五月的倉頡發表於2015-10-05

公平鎖與非公平鎖

ReentrantLock有一個很大的特點,就是可以指定鎖是公平鎖還是非公平鎖,公平鎖表示執行緒獲取鎖的順序是按照執行緒排隊的順序來分配的,而非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得鎖的,先來的未必就一定能先得到鎖,從這個角度講,synchronized其實就是一種非公平鎖。非公平鎖的方式可能造成某些執行緒一直拿不到鎖,自然是非公平的了。看一下例子,new ReentrantLock的時候有一個單一引數的建構函式表示構造的是一個公平鎖還是非公平鎖,傳入true就可以了:

public class ThreadDomain42
{
    private Lock lock = new ReentrantLock(true);
    
    public void testMethod()
    {
        try
        {
            lock.lock();
            System.out.println("ThreadName" + Thread.currentThread().getName() + "獲得鎖");
        }
        finally
        {
            lock.unlock();
        }
    }
}
public static void main(String[] args) throws Exception
{
    final ThreadDomain42 td = new ThreadDomain42();
    Runnable runnable = new Runnable()
    {
        public void run()
        {
            System.out.println("◆執行緒" + Thread.currentThread().getName() + "執行了");
            td.testMethod();
        }
    };
    Thread[] threads = new Thread[5];
    for (int i = 0; i < 5; i++)
        threads[i] = new Thread(runnable);
    for (int i = 0; i < 5; i++)
        threads[i].start();
}

看一下執行結果:

◆執行緒Thread-0執行了
◆執行緒Thread-3執行了
ThreadNameThread-0獲得鎖
◆執行緒Thread-2執行了
◆執行緒Thread-1執行了
ThreadNameThread-3獲得鎖
◆執行緒Thread-4執行了
ThreadNameThread-2獲得鎖
ThreadNameThread-1獲得鎖
ThreadNameThread-4獲得鎖

我們的程式碼很簡單,一執行run()方法的第一步就是嘗試獲得鎖。看到結果裡面獲得鎖的順序和執行緒啟動順序是一致的,這就是公平鎖。對比一下,如果是非公平鎖執行結果是怎麼樣的,在new ReentrantLock的時候傳入false:

◆執行緒Thread-1執行了
◆執行緒Thread-2執行了
◆執行緒Thread-0執行了
ThreadNameThread-1獲得鎖
ThreadNameThread-2獲得鎖
◆執行緒Thread-3執行了
◆執行緒Thread-4執行了
ThreadNameThread-3獲得鎖
ThreadNameThread-0獲得鎖
ThreadNameThread-4獲得鎖

執行緒啟動順序是1 2 0 3 4,獲得鎖的順序卻是1 2 3 0 4,這就是非公平鎖,它不保證先排隊嘗試去獲取鎖的執行緒一定能先拿到鎖

 

getHoldCount()

getHoldCount()方法返回的是當前執行緒呼叫lock()的次數,看一下例子:

public class ThreadDomain43
{
    private ReentrantLock lock = new ReentrantLock();
    
    public void testMethod1()
    {
        try
        {
            lock.lock();
            System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
            testMethod2();
        }
        finally
        {
            lock.unlock();
        }
    }
    
    public void testMethod2()
    {
        try
        {
            lock.lock();
            System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
        }
        finally
        {
            lock.unlock();
        }
    }
}
public static void main(String[] args)
{
    ThreadDomain43 td = new ThreadDomain43();
    td.testMethod1();
}

看一下執行結果:

testMethod1 getHoldCount = 1
testMethod2 getHoldCount = 2

ReentrantLock和synchronized一樣,鎖都是可重入的,同一執行緒的同一個ReentrantLock的lock()方法被呼叫了多少次,getHoldCount()方法就返回多少

 

getQueueLength()和isFair()

getQueueLength()方法用於獲取正等待獲取此鎖定的執行緒估計數。注意"估計"兩個字,因為此方法遍歷內部資料結構的同時,執行緒的資料可能動態變化

isFair()用來獲取此鎖是否公平鎖

看一下例子:

public class ThreadDomain44
{
    public ReentrantLock lock = new ReentrantLock();
    
    public void testMethod()
    {
        try
        {
            lock.lock();
            System.out.println("ThreadName = " + Thread.currentThread().getName() + "進入方法!");
            System.out.println("是否公平鎖?" + lock.isFair());
            Thread.sleep(Integer.MAX_VALUE);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }
}
public static void main(String[] args) throws InterruptedException
{
    final ThreadDomain44 td = new ThreadDomain44();
    Runnable runnable = new Runnable()
    {
        public void run()
        {
            td.testMethod();
        }
    };
    Thread[] threads = new Thread[10];
    for (int i = 0; i < 10; i++)
        threads[i] = new Thread(runnable);
    for (int i = 0; i < 10; i++)
        threads[i].start();
    Thread.sleep(2000);
    System.out.println("有" + td.lock.getQueueLength() + "個執行緒正在等待!");
}

看一下執行結果:

ThreadName = Thread-0進入方法!
是否公平鎖?false
有9個執行緒正在等待!

ReentrantLock預設的是非公平鎖,因此是否公平鎖列印的是false。啟動了10個執行緒,只有1個執行緒lock()了,其餘9個等待,都符合預期。

 

hasQueuedThread()和hasQueuedThreads()

hasQueuedThread(Thread thread)用來查詢指定的執行緒是否正在等待獲取指定的物件監視器

hasQueuedThreads()用於查詢是否有執行緒正在等待獲取指定的物件監視器

看一下例子,換一個寫法,ReentrantLock既然是一個類,就有類的特性,所以這次用繼承ReentrantLock的寫法,這也是很常見的:

public class ThreadDomain45 extends ReentrantLock
{
    public void waitMethod()
    {
        try
        {
            lock();
            Thread.sleep(Integer.MAX_VALUE);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            unlock();
        }
    }
}
public static void main(String[] args) throws InterruptedException
{
    final ThreadDomain45 td = new ThreadDomain45();
    Runnable runnable = new Runnable()
    {
        public void run()
        {
            td.waitMethod();
        }
    };
    Thread t0 = new Thread(runnable);
    t0.start();
    Thread.sleep(500);
    Thread t1 = new Thread(runnable);
    t1.start();
    Thread.sleep(500);
    Thread t2 = new Thread(runnable);
    t2.start();
    Thread.sleep(500);
    System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
    System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
    System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
    System.out.println("is any thread waiting?" + td.hasQueuedThreads());
}

這裡加了幾個Thread.sleep(500)保證執行緒按順序啟動(其實不按順序啟動也關係不大),看一下執行結果:

t0 is waiting?false
t1 is waiting?true
t2 is waiting?true
is any thread waiting?true

由於t0先啟動獲得了鎖,因此不等待,返回false,另外兩個執行緒則要等待獲取t0的鎖,因此返回的是true,而此ReentrantLock中有執行緒在等待,所以hasQueuedThreads()返回的是true

 

isHeldByCurrentThread()和isLocked()

isHeldByCurrentThread()表示此物件監視器是否由當前執行緒保持

isLocked()表示此物件監視器是否由任意執行緒保持

看一下例子:

public class ThreadDomain46 extends ReentrantLock
{
    public void testMethod()
    {
        try
        {
            lock();
            System.out.println(Thread.currentThread().getName() + "執行緒持有了鎖!");
            System.out.println(Thread.currentThread().getName() + "執行緒是否持有鎖?" + 
                    isHeldByCurrentThread());
            System.out.println("是否任意執行緒持有了鎖?" + isLocked());
            Thread.sleep(Integer.MAX_VALUE);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            unlock();
        }
    }
    
    public void testHoldLock()
    {
        System.out.println(Thread.currentThread().getName() + "執行緒是否持有鎖?" + 
                isHeldByCurrentThread());
        System.out.println("是否任意執行緒持有了鎖?" + isLocked());
    }
}
public static void main(String[] args)
{
    final ThreadDomain46 td = new ThreadDomain46();
    Runnable runnable0 = new Runnable()
    {
        public void run()
        {
            td.testMethod();
        }
    };
    Runnable runnable1 = new Runnable()
    {
        public void run()
        {
            td.testHoldLock();
        }
    };
    Thread t0 = new Thread(runnable0);
    Thread t1 = new Thread(runnable1);
    t0.start();
    t1.start();
}

看一下執行結果:

Thread-0執行緒持有了鎖!
Thread-1執行緒是否持有鎖?false
Thread-0執行緒是否持有鎖?true
是否任意執行緒持有了鎖?true
是否任意執行緒持有了鎖?true

這個應該很好理解,當前持有鎖的是Thread-0執行緒,所以對於Thread-1來說自然不持有鎖。

 

tryLock()和tryLock(long timeout, TimeUnit unit)

tryLock()方法的作用是,在呼叫try()方法的時候,如果鎖沒有被另外一個執行緒持有,那麼就返回true,否則返回false

tryLock(long timeout, TimeUnit unit)是tryLock()另一個重要的過載方法,表示如果在指定等待時間內獲得了鎖,則返回true,否則返回false

注意一下,tryLock()只探測鎖是否,並沒有lock()的功能,要獲取鎖,還得呼叫lock()方法,看一下tryLock()的例子:

public class ThreadDomain47 extends ReentrantLock
{
    public void waitMethod()
    {
        if (tryLock())
            System.out.println(Thread.currentThread().getName() + "獲得了鎖");
        else
            System.out.println(Thread.currentThread().getName() + "沒有獲得鎖");
    }
}
public static void main(String[] args)
{
    final ThreadDomain47 td = new ThreadDomain47();
    Runnable runnable = new Runnable()
    {
        public void run()
        {
            td.waitMethod();
        }
    };
    Thread t0 = new Thread(runnable);
    Thread t1 = new Thread(runnable);
    t0.start();
    t1.start();
}

看一下執行結果:

Thread-0獲得了鎖
Thread-1沒有獲得鎖

第一個執行緒獲得了鎖返回true,第二個執行緒自然返回的false。由於有了tryLock()這種機制,如果一個執行緒長時間在synchronzied程式碼/synchronized程式碼塊之中,別的執行緒不得不長時間無限等待的情況將可以被避免。

 

ReentrantLock中的其他方法

篇幅原因,ReentrantLock中還有很多沒有被列舉到的方法就不寫了,看一下它們的作用:

1、getWaitQueueLength(Condition condition)

類似getQueueLength(),不過此方法的前提是condition。比如5個執行緒,每個執行緒都執行了同一個await()的await()方法,那麼方法呼叫的返回值是5,因為5個執行緒都在等待獲得鎖

2、hasWaiters(Condition condition)

查詢是否有執行緒正在等待與此鎖有關的condition條件。比如5個執行緒,每個執行緒都執行了同一個condition的await()方法,那麼方法呼叫的返回值是true,因為它們都在等待condition

3、lockInterruptibly()

如果當前執行緒未被中斷,則獲取鎖

4、getWaitingThreads(Condition condition)

返回一個collection,它包含可能正在等待與此鎖相關給定條件的那些執行緒,因為構造結果的時候實際執行緒可能動態變化,因此返回的collection只是盡力的估計值

相關文章