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里就有。

相关文章
相关标签/搜索