java 多线程 19: 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锁的就是lock这个对象,而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里就有。

Condition可以理解为,Condition声明多个的时候,一个lock里面有分支来判断唤醒哪个Condition,但是多个Condition只是共享一个锁的监视器的,这个wait一样,不同的是wait只支持一个对象就是当前锁对象,而Condition支持多个就是当前Condition这个对象和lock绑在一起。

注意这里如果是用Condition 实现生产消费模型,同理也是要使用signalAll()方法进行全部唤醒,否则也会出现线程假死情况,还可以分两个Condition ,一个代表生产者,一个代表消费者,唤醒停止对应的Condition 就可以不用signalAll


时间: 2024-10-02 23:29:22

java 多线程 19: ReentrantLock 与 Condition的相关文章

Java多线程19:定时器Timer

前言 定时/计划功能在Java应用的各个领域都使用得非常多,比方说Web层面,可能一个项目要定时采集话单.定时更新某些缓存.定时清理一批不活跃用户等等.定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程方式进行处理,所以它和多线程技术关联还是相当大的.那和ThreadLocal一样,还是先讲原理再讲使用,Timer的实现原理不难,就简单扫一下就好了. Timer的schedule(TimeTask task, Date time)的使用 该方法的作用是在执行的日期执行一

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

ReentrantLock ReentrantLock,一个可重入的互斥锁,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. ReentrantLock基本用法 先来看一下ReentrantLock的基本用法: public class ThreadDomain38 { private Lock lock = new ReentrantLock(); public void testMethod() { try { lock.lock()

Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取.ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在

java多线程-Condition

Condition 将 Object 监视器方法(wait.notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set).其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用. 先看一个关于Condition使用的简单实例: 1 public class ConditionTest { 2 public static voi

Java多线程之ReentrantLock与Condition

一.ReentrantLock 类 1.1 什么是reentrantlock java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现.这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法.性能特性或者锁定语义. ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票.定时锁等候和可中断锁等候的一些特性.此外

java多线程,多线程加锁以及Condition类的使用

看了网上非常多的运行代码,很多都是重复的再说一件事,可能对于java老鸟来说,理解java的多线程是非常容易的事情,但是对于我这样的菜鸟来说,这个实在有点难,可能是我太菜了,网上重复的陈述对于我理解这个问题一点帮助都没有.所以这里我写下我对于这个问题的理解,目的是为了防止我忘记. 还是从代码实例开始讲起: import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.co

java多线程 -- Condition 控制线程通信

Api文档如此定义: Condition 将 Object 监视器方法(wait.notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set).其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用. Condition 接口描述了可能会与锁有关联的条件变量.这些变量在用法上与使用 Object.wait 访问的隐式监视器类似

java 多线程之ReentrantLock与condition

参考链接:https://blog.csdn.net/vernonzheng/article/details/8288251 ReentrantLock 类 1.1 什么是reentrantlock java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现.这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法.性能特性或者锁定语义. ReentrantLock 类实现了 Lock ,

JAVA多线程按指定顺序执行线程 Condition应用

package concurrent; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * Auth: zhouhongliang * Date:2019/8/1 * 线程等待与唤醒机制 Condition * 按指定顺序执行线程 */ public class ConditionDemo { public static void main(Stri