`

重入锁

阅读更多
重入锁(ReentrantLock)是一种递归无阻塞的同步机制。以前一直认为它是synchronized的简单替代,而且实现机制也不相差太远。不过最近实践过程中发现它们之间还是有着天壤之别。

以下是官方说明:一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。

它提供了lock()方法:
如果该锁定没有被另一个线程保持,则获取该锁定并立即返回,将锁定的保持计数设置为 1。
如果当前线程已经保持该锁定,则将保持计数加 1,并且该方法立即返回。
如果该锁定被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁定之前,该线程将一直处于休眠状态,此时锁定保持计数被设置为 1。

最近在研究Java concurrent中关于任务调度的实现时,读了延迟队列DelayQueue的一些代码,比如take()。该方法的主要功能是从优先队列(PriorityQueue)取出一个最应该执行的任务(最优值),如果该任务的预订执行时间未到,则需要wait这段时间差。反之,如果时间到了,则返回该任务。而offer()方法是将一个任务添加到该队列中。

后来产生了一个疑问:如果最应该执行的任务是一个小时后执行的,而此时需要提交一个10秒后执行的任务,会出现什么状况?还是先看看take()的源代码:


public E take() throws InterruptedException {
                final ReentrantLock lock = this.lock;
                lock.lockInterruptibly();
                try {
                    for (;;) {
                        E first = q.peek();
                        if (first == null) {
                            available.await();
                        } else {
                            long delay =  first.getDelay(TimeUnit.NANOSECONDS);
                            if (delay > 0) {
                                long tl = available.awaitNanos(delay);
                            } else {
                                E x = q.poll();
                                assert x != null;
                                if (q.size() != 0)
                                    available.signalAll(); // wake up other takers
                                return x;
                            }
                        }
                    }
                } finally {
                    lock.unlock();
                }
            } 

而以下是offer()的源代码:


public boolean offer(E e) {
                final ReentrantLock lock = this.lock;
                lock.lock();
                try {
                    E first = q.peek();
                    q.offer(e);
                    if (first == null || e.compareTo(first) < 0)
                        available.signalAll();
                    return true;
                } finally {
                    lock.unlock();
                }
            } 



如代码所示,take()和offer()都是lock了重入锁。如果按照synchronized的思维(使用诸如synchronized(obj)的方法),这两个方法是互斥的。回到刚才的疑问,take()方法需要等待1个小时才能返回,而offer()需要马上提交一个10秒后运行的任务,会不会一直等待take()返回后才能提交呢?答案是否定的,通过编写验证代码也说明了这一点。这让我对重入锁有了更大的兴趣,它确实是一个无阻塞的锁。

下面的代码也许能说明问题:运行了4个线程,每一次运行前打印lock的当前状态。运行后都要等待5秒钟。

public static void main(String[] args) throws InterruptedException {
              final ExecutorService exec = Executors.newFixedThreadPool(4);
              final ReentrantLock lock = new ReentrantLock();
              final Condition con = lock.newCondition();
              final int time = 5;
              final Runnable add = new Runnable() {
                public void run() {
                  System.out.println("Pre " + lock);
                  lock.lock();
                  try {
                    con.await(time, TimeUnit.SECONDS);
                  } catch (InterruptedException e) {
                    e.printStackTrace();
                  } finally {
                    System.out.println("Post " + lock.toString());
                    lock.unlock();
                  }
                }
              };
              for(int index = 0; index < 4; index++)
                exec.submit(add);
              exec.shutdown();
            } 



这是它的输出:
Pre ReentrantLock@a59698[Unlocked]
Pre ReentrantLock@a59698[Unlocked]
Pre ReentrantLock@a59698[Unlocked]
Pre ReentrantLock@a59698[Unlocked]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-1]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-2]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-3]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-4]

每一个线程的锁状态都是“Unlocked”,所以都可以运行。但在把con.await改成Thread.sleep(5000)时,输出就变成了:
Pre ReentrantLock@a59698[Unlocked]
Pre ReentrantLock@a59698[Locked by thread pool-1-thread-1]
Pre ReentrantLock@a59698[Locked by thread pool-1-thread-1]
Pre ReentrantLock@a59698[Locked by thread pool-1-thread-1]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-1]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-2]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-3]
Post ReentrantLock@a59698[Locked by thread pool-1-thread-4]


以上的对比说明线程在等待时(con.await),已经不在拥有(keep)该锁了,所以其他线程就可以获得重入锁了。


有必要会过头再看看Java官方的解释:“如果该锁定被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁定之前,该线程将一直处于休眠状态”。我对这里的“保持”的理解是指非wait状态外的所有状态,比如线程Sleep、for循环等一切有CPU参与的活动。一旦线程进入wait状态后,它就不再keep这个锁了,其他线程就可以获得该锁;当该线程被唤醒(触发信号或者timeout)后,就接着执行,会重新“保持”锁,当然前提依然是其他线程已经不再“保持”了该重入锁。

总结一句话:对于重入锁而言,"lock"和"keep"是两个不同的概念。lock了锁,不一定keep锁,但keep了锁一定已经lock了锁。
分享到:
评论

相关推荐

    手写可重入锁

    可重入锁,也称为递归锁,是Java并发编程中的一个重要概念,主要在`java.util.concurrent.locks.ReentrantLock`类中实现。这个概念对于理解多线程环境下的同步控制至关重要,尤其对于初学者来说,掌握其原理和用法...

    自己动手写一把可重入锁测试案例

    本篇将基于《Java并发编程:自己动手写一把可重入锁》一文中的案例,深入探讨可重入锁的概念、原理以及如何实现一个简单的可重入锁。 可重入锁,顾名思义,就是可以被同一线程重复获取的锁。当一个线程已经持有锁的...

    redislock-基于redis的分布式可重入锁

    分布式可重入锁是分布式系统中解决并发控制和同步问题的关键技术之一,特别是在微服务架构中,多个服务可能需要共享同一资源,此时就需要一种机制来确保数据的一致性和正确性。Redis,作为一个高性能的键值存储系统...

    各种锁汇总,乐观锁、悲观锁、分布式锁、可重入锁、互斥锁、读写锁、分段锁、类锁、行级锁等

    本文将深入探讨标题和描述中提及的各种锁,包括乐观锁、悲观锁、分布式锁、可重入锁、互斥锁、读写锁、分段锁、类锁以及行级锁。 1. **乐观锁**:乐观锁假设多线程环境中的冲突较少,所以在读取数据时不加锁,只有...

    Java可重入锁的实现原理与应用场景

    Java可重入锁的实现原理与应用场景 Java可重入锁是一种特殊的锁机制,允许同一个线程在不同的层次上获取同一个锁,而不会发生死锁或阻塞的情况。这种锁机制广泛应用于多线程编程中,用于解决线程之间的同步问题。 ...

    Java多线程高并发篇(一)--重入锁

    在Java多线程高并发编程中,重入锁(ReentrantLock)是一个至关重要的概念,它提供了比Java内置锁(synchronized)更细粒度的控制,并且具有更高的可读性和可扩展性。本篇文章将深入探讨重入锁的相关知识点。 首先...

    JUC最详细思维导图,一次了解读写锁,可重入锁,Cas原理,volatile 关键字原理

    本文将深入探讨其中的关键概念,包括读写锁、可重入锁、CAS原理以及volatile关键字。 首先,我们来看读写锁。读写锁允许多个线程同时进行读操作,但在写操作时,只有一个线程能够获得锁。这种设计极大地提高了并发...

    Go《Redis实现智能门锁(互斥锁、看门狗、读写锁、红锁、闭锁、可重入锁)》+源代码+设计资料

    Redis实现互斥锁、看门狗、读写锁、红锁、闭锁、可重入锁 - 不懂运行,下载完可以私聊问,可远程教学 该资源内项目源码是个人的毕设,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载...

    Reentranlock重入锁源码流程图,私聊关注免费给

    重入锁非公平实现方式 线程的状态标识 变量 waitStatus 则表示当前 Node 结点的等待状态,共有5种取值 CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。 CANCELLED(1):表示当前结点已取消调度。当timeout或被中断...

    Java源码解析之可重入锁ReentrantLock

    Java源码解析之可重入锁ReentrantLock ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock。它是一个可重入的排他锁,它和synchronized的方法和代码有着相同的行为和语义,但有更多的功能。 ...

    Java基于数据库的分布式可重入锁(带等待时间和过期时间)

    Java基于数据库的分布式可重入锁(带等待时间和过期时间)

    java代码-证明synchronized可重入锁

    首先,`synchronized`可重入锁的原理是,每个线程在尝试获取锁时都会在内部维护一个锁计数器。当线程首次获得锁时,计数器设为1,如果该线程再次请求同一锁,计数器会递增,释放锁时则递减。只有当计数器归零时,锁...

    举例讲解Python中的死锁、可重入锁和互斥锁

    ### 举例讲解Python中的死锁、可重入锁和互斥锁 #### 一、死锁 **死锁**是一种程序状态,在这种状态下,两个或多个进程或线程因为互相等待对方持有的资源而不释放自己的资源,导致它们都无法继续执行下去的情况。...

    Zookeeper 分布式重入排它锁实现

    4. **重入性**:重入锁允许一个线程多次获取同一把锁,而不会被自己阻塞。在Zookeeper中,通过客户端维护一个计数器来实现这一特性。 **二、SpringBoot集成Zookeeper实现分布式锁** 1. **引入依赖**:在SpringBoot...

    教你完全理解ReentrantLock重入锁

    ReentrantLock支持重入性,意味着一个线程可以多次进入同一段被该锁保护的代码,只要这个线程还没有释放这个锁。这种特性对于某些复杂并发场景特别有用。 在ReentrantLock中,重入性是通过维护每个锁的同步状态(即...

    Java锁之可重入锁介绍

    可重入锁是一种特殊的锁机制,它允许同一个线程在已获取锁的状态下再次请求同一把锁,而不会导致死锁。在Java中,可重入锁的主要实现有两种:`synchronized` 关键字和 `java.util.concurrent.locks.ReentrantLock` ...

    简单了解Java中的可重入锁

    在Java编程语言中,可重入锁是一种特殊类型的锁,允许同一个线程多次获取同一锁。这种机制在处理递归调用或者嵌套同步块时非常有用,因为它防止了死锁的发生。本文将深入探讨Java中的可重入锁,包括其原理、使用以及...

    Java并发编程之重入锁与读写锁

    Java并发编程之重入锁与读写锁 在 Java 并发编程中,重入锁和读写锁是两个非常重要的概念。重入锁是指支持重进入的锁,也就是说,一个线程可以多次获取同一个锁,而不会被阻塞。读写锁则是维护了一对相关的锁,一...

    读写锁,不支持重入

    在不支持重入的情况下,这意味着一旦线程获得了读锁或写锁,它无法再次获取同一类型的锁,直到释放当前持有的锁。这种设计有助于防止死锁,并在某些情况下提高并发性能。 读写锁的核心概念包括读锁和写锁。读锁用于...

Global site tag (gtag.js) - Google Analytics