Java多線程12:ReentrantLock中的方法


公平鎖與非公平鎖

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只是盡力的估計值


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM