zoukankan      html  css  js  c++  java
  • Java多线程11:ReentrantLock的使用和Condition

    ReentrantLock

    ReentrantLock,一个可重入的互斥锁,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

    ReentrantLock基本用法

    先来看一下ReentrantLock的基本用法:

    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具有加锁的功能

    ReentrantLock持有的是对象监视器

    前面已经证明了ReentrantLock具有加锁功能,但我们还不知道ReentrantLock持有的是什么锁,因此写个例子看一下:

    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()方法:

    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有更好的灵活性,具体体现在:

    1、一个Lock里面可以创建多个Condition实例,实现多路通知

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

  • 相关阅读:
    BZOJ2821 作诗(Poetize) 【分块】
    BZOJ2724 蒲公英 【分块】
    Codeforces 17E Palisection 【Manacher】
    BZOJ2565 最长双回文串 【Manacher】
    Codeforces 25E Test 【Hash】
    CODEVS3013 单词背诵 【Hash】【MAP】
    HDU2825 Wireless Password 【AC自动机】【状压DP】
    HDU2896 病毒侵袭 【AC自动机】
    HDU3065 病毒侵袭持续中【AC自动机】
    HDU2222 Keywords Search 【AC自动机】
  • 原文地址:https://www.cnblogs.com/faunjoe88/p/7903862.html
Copyright © 2011-2022 走看看