Java多線程11:ReentrantLock的使用和Condition

ReentrantLock異步

ReentrantLock,一個可重入的互斥鎖,它具備與使用synchronized方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。函數

 

ReentrantLock基本用法this

先來看一下ReentrantLock的基本用法:spa

public class ThreadDomain38
{
    private Lock lock = new ReentrantLock();
    
    public void testMethod()
    {
        try
        {
            lock.lock();
            for (int i = 0; i < 2; i++)
            {
                System.out.println("ThreadName = " + Thread.currentThread().getName() + 
                        ", i  = " + i);
            }
        }
        finally
        {
            lock.unlock();
        }
    }
}
public class MyThread38 extends Thread
{
    private ThreadDomain38 td;
    
    public MyThread38(ThreadDomain38 td)
    {
        this.td = td;
    }
    
    public void run()
    {
        td.testMethod();
    }
}
public static void main(String[] args)
{
    ThreadDomain38 td = new ThreadDomain38();
    MyThread38 mt0 = new MyThread38(td);
    MyThread38 mt1 = new MyThread38(td);
    MyThread38 mt2 = new MyThread38(td);
    mt0.start();
    mt1.start();
    mt2.start();
}

看一下運行結果:線程

ThreadName = Thread-1, i  = 0
ThreadName = Thread-1, i  = 1
ThreadName = Thread-0, i  = 0
ThreadName = Thread-0, i  = 1
ThreadName = Thread-2, i  = 0
ThreadName = Thread-2, i  = 1

沒有任何的交替,數據都是分組打印的,說明了一個線程打印完畢以後下一個線程才能夠得到鎖去打印數據,這也證實了ReentrantLock具備加鎖的功能code

 

ReentrantLock持有的是對象監視器對象

前面已經證實了ReentrantLock具備加鎖功能,但咱們還不知道ReentrantLock持有的是什麼鎖,所以寫個例子看一下:blog

public class ThreadDomain39
{
    private Lock lock = new ReentrantLock();
    
    public void methodA()
    {
        try
        {
            lock.lock();
            System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName());
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
        
    }
    
    public void methodB()
    {
        lock.lock();
        System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
        System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
        lock.unlock();
    }
}

寫兩個線程分別調用methodA()和methodB()方法:get

public class MyThread39_0 extends Thread
{
    private ThreadDomain39 td;
    
    public MyThread39_0(ThreadDomain39 td)
    {
        this.td = td;
    }
    
    public void run()
    {
        td.methodA();
    }
}
public class MyThread39_1 extends Thread
{
    private ThreadDomain39 td;
    
    public MyThread39_1(ThreadDomain39 td)
    {
        this.td = td;
    }
    
    public void run()
    {
        td.methodB();
    }
}

寫一個main函數啓動這兩個線程:虛擬機

public static void main(String[] args)
{
    ThreadDomain39 td = new ThreadDomain39();
    MyThread39_0 mt0 = new MyThread39_0(td);
    MyThread39_1 mt1 = new MyThread39_1(td);
    mt0.start();
    mt1.start();
}

看一下運行結果:

MethodB begin ThreadName = Thread-1
MethodB begin ThreadName = Thread-1
MethodA begin ThreadName = Thread-0
MethodA end ThreadName = Thread-0

看不見時間,不過第四確實是格了5秒左右纔打印出來的。從結果來看,已經證實了ReentrantLock持有的是對象監視器,能夠寫一段代碼進一步證實這一結論,即去掉methodB()內部和鎖相關的代碼,只留下兩句打印語句:

MethodA begin ThreadName = Thread-0
MethodB begin ThreadName = Thread-1
MethodB begin ThreadName = Thread-1
MethodA end ThreadName = Thread-0

看到交替打印了,進一步證實了ReentrantLock持有的是"對象監視器"的結論。

不過注意一點,ReentrantLock雖然持有對象監視器,可是和synchronized持有的對象監視器不是一個意思,雖然我也不清楚兩個持有的對象監視器有什麼區別,不過把methodB()方法用synchronized修飾,methodA()不變,兩個方法仍是異步運行的,因此就記一個結論吧----ReentrantLock和synchronized持有的對象監視器不一樣

另外,千萬別忘了,ReentrantLock持有的鎖是須要手動去unlock()的

 

Condition

synchronized與wait()和nitofy()/notifyAll()方法相結合能夠實現等待/通知模型,ReentrantLock一樣能夠,可是須要藉助Condition,且Condition有更好的靈活性,具體體如今:

一、一個Lock裏面能夠建立多個Condition實例,實現多路通知

二、notify()方法進行通知時,被通知的線程時Java虛擬機隨機選擇的,可是ReentrantLock結合Condition能夠實現有選擇性地通知,這是很是重要的

看一下利用Condition實現等待/通知模型的最簡單用法,下面的代碼注意一下,await()和signal()以前,必需要先lock()得到鎖,使用完畢在finally中unlock()釋放鎖,這和wait()/notify()/notifyAll()使用前必須先得到對象鎖是同樣的:

public class ThreadDomain40
{
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    
    public void await()
    {
        try
        {
            lock.lock();
            System.out.println("await時間爲:" + System.currentTimeMillis());
            condition.await();
            System.out.println("await等待結束");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
    }
    
    public void signal()
    {
        try
        {
            lock.lock();
            System.out.println("signal時間爲:" + System.currentTimeMillis());
            condition.signal();
        }
        finally
        {
            lock.unlock();
        }
    }
}
public class MyThread40 extends Thread
{
    private ThreadDomain40 td;
    
    public MyThread40(ThreadDomain40 td)
    {
        this.td = td;
    }
    
    public void run()
    {
        td.await();
    }
}
public static void main(String[] args) throws Exception
{
    ThreadDomain40 td = new ThreadDomain40();
    MyThread40 mt = new MyThread40(td);
    mt.start();
    Thread.sleep(3000);
    td.signal();
}

看一下運行結果:

await時間爲:1443970329524
signal時間爲:1443970332524
await等待結束

差值是3000毫秒也就是3秒,符合代碼預期,成功利用ReentrantLock的Condition實現了等待/通知模型。其實這個例子還證實了一點,Condition的await()方法是釋放鎖的,緣由也很簡單,要是await()方法不釋放鎖,那麼signal()方法又怎麼能調用到Condition的signal()方法呢?

注意要是用一個Condition的話,那麼多個線程被該Condition給await()後,調用Condition的signalAll()方法喚醒的是全部的線程。若是想單獨喚醒部分線程該怎麼辦呢?new出多個Condition就能夠了,這樣也有助於提高程序運行的效率。使用多個Condition的場景是很常見的,像ArrayBlockingQueue裏就有。

相關文章
相關標籤/搜索