`
tenyears
  • 浏览: 206522 次
  • 性别: Icon_minigender_1
  • 来自: 湖北武汉
文章分类
社区版块
存档分类
最新评论

再谈重入锁--ReentrantLock

阅读更多

入锁(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了锁。

分享到:
评论
13 楼 xlshl43 2017-05-27  
感觉有点误认子弟!!
“如果按照synchronized的思维(使用诸如synchronized(obj)的方法),这两个方法是互斥的。”这句跟哪个锁没关系,主要看你是用sleep还是用await/wait
12 楼 xlshl43 2017-05-27  
sulpha 写道
"它确实是一个无阻塞的锁"这种说法有所不妥。

其实核心在于Condition.await()。JDK里面的文档:
The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes...

也就是与available绑定的lock在调用available.await()时,lock会被释放。在await()返回后(准确点是返回之前),lock会重新获得。JDK里面的原话:
In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.

10楼说的对!!!
11 楼 生活小丑 2014-04-10  
其实重点就是await的问题
10 楼 sulpha 2013-11-28  
"它确实是一个无阻塞的锁"这种说法有所不妥。

其实核心在于Condition.await()。JDK里面的文档:
The lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes...

也就是与available绑定的lock在调用available.await()时,lock会被释放。在await()返回后(准确点是返回之前),lock会重新获得。JDK里面的原话:
In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.
9 楼 淘气天空lc 2013-08-12  
如果该锁定被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁定之前,该线程将一直处于休眠状态,此时锁定保持计数被设置为 1。  这个意思是线程锁不会释放吧?????????????
8 楼 fenshen6046 2012-03-29  
java官方文档Condition的await描述
await

void await()
           throws InterruptedException

    造成当前线程在接到信号或被中断之前一直处于等待状态。

    与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:

        其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者
        其他某个线程调用此 Condition 的 signalAll() 方法;或者
        其他某个线程中断当前线程,且支持中断线程的挂起;或者
        发生“虚假唤醒”
7 楼 zephyrum 2012-02-09  
amos_tl 写道
原子操作 底层也是用的 synchronized

Atomic类底层用的是sun.misc.unsafe里的系统级别cas操作,没有synchronize。
6 楼 amos_tl 2011-08-31  
原子操作 底层也是用的 synchronized
5 楼 amos_tl 2011-08-31  
引用

推荐一下原子操作,更简便,实用!java.concurrent.atomic下的几种常用类型也很全了,性能比lock好!
前提是不会出现race condition!
当然LOCK比synchronized好多了!lock()与unlock()结合在try.finally块中,使用也比较方便!


原子操作底层也是使用的synchronized.
4 楼 lordhong 2007-01-21  
多谢,受益非浅
3 楼 YuLimin 2007-01-20  
推荐一篇我在IBM DW上面看到的不错的文章

《Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制》

http://www.ibm.com/developerworks/cn/java/j-jtp10264/index.html
2 楼 歆渊 2007-01-20  
DelayQueue 实现里面只用到了一个 Lock 和 它的一个 Condition 的组合, 这个没有超出原有 synchronized + Object.wait() + Object.notify() 的范围, 新的同步机制的好处, 比如 公平竞争, 单资源多条件 等等都没有派上用场.

在对一个对象的 synchronized 块中 Object.wait(); 过程中, 同样是不会阻塞其它线程进入该对象的 synchronized 块的. 所以它目前的实现是完全可以用原有机制代替的

现在的代码执行速度方面不知道是不是一定有提升, 但从内存占用角度看, 还不如原有机制效率高.
1 楼 galaxystar 2007-01-20  
推荐一下原子操作,更简便,实用!java.concurrent.atomic下的几种常用类型也很全了,性能比lock好!
前提是不会出现race condition!
当然LOCK比synchronized好多了!lock()与unlock()结合在try.finally块中,使用也比较方便!

相关推荐

    详解java并发之重入锁-ReentrantLock

    主要介绍了java并发之重入锁-ReentrantLock,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    Java 多线程与并发(11-26)-JUC锁- ReentrantLock详解.pdf

    Java 多线程与并发(11_26)-JUC锁_ ReentrantLock详解

    jnpr-eychen#jun_ppt#锁-重入锁(ReentrantLock)1

    公平和非公平选择这里提到一个锁获取的公平性问题,如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。public void

    教你完全理解ReentrantLock重入锁

    ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字synchronized隐式支持重...

    带你看看Java的锁(一)-ReentrantLock

    带你看看Javad的锁-ReentrantLock前言ReentrantLock简介Synchronized对比用法源码分析代码结构方法分析SyncNonfairSyncFairSync非公平锁VS公平锁什么是公平非公平ReentrantLockReentrantLock的构造函数lock加锁方法...

    java ReentrantLock详解.docx

    ReentrantLock ...ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。 3.synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。

    Java多线程之ReentrantLock与Condition - 平凡希 - 博客园1

    1、ReentrantLock简介 2、ReentrantLock函数列表 3、重入的实现 4、公平锁与非公平锁 5、ReentrantLock 扩展的功能 6

    ReentrantLock源码详解--公平锁、非公平锁

    ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。下面我们来深入了解一下它吧

    Java中ReentrantLock的使用.docx

    重入锁ReentrantLock 相对来说是synchronized、Object.wait()和Object.notify()方法的替代品(或者说是增强版),在JDK5.0的早期版本,重入锁的性能远远好于synchronized,但从JDK6.0开始,JDK在synchronized上做了...

    Java源码解析之可重入锁ReentrantLock

    今天小编就为大家分享一篇关于Java源码解析之可重入锁ReentrantLock,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

    第六章 ReentrantLock源码解析2--释放锁unlock()1

    第六章 ReentrantLock源码解析2--释放锁unlock()最常用的方式://注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁

    Java重入锁ReentrantLock

     从使用场景的角度出发来介绍对ReentrantLock的使用,相对来说容易理解一些。  场景1:如果发现该操作已经在执行中则不再执行(有状态执行)  a、用在定时任务时,如果任务执行时间可能超过下次计划执行时间,...

    第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()1

    第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()最常用的方式://注意:通常情况下,这个会设置成一个类变量,比如说Segemen

    ReentrantLock源码详解--条件锁

    主要介绍了ReentrantLock源码之条件锁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面我们来一起学习一下吧

    ReentrantLock与synchronized

    助于理解的例子 博文链接:https://uule.iteye.com/blog/1488356

    locks框架_ReentrantLock.pdf

    解释为什么它被称为“可重入锁”,以及如何解决传统锁可能的问题。 ReentrantLock 的基本用法: 深入探讨如何使用 ReentrantLock 来保护共享资源。演示如何通过 lock 和 unlock 方法来实现线程的同步和互斥。 ...

    Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

    主要介绍了Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁,本文讲解了ReentrantLock概况、Lock接口、Lock使用、轮询锁的和定时锁、公平性、可中断获锁获取操作等内容,需要的朋友可以参考下

    ReentrantLock与synchronized区别

    java语言 并发编程 ReentrantLock与synchronized区别 详解

    ReentrantLock的使用及注意事项

    ReentrantLock的使用及注意事项

Global site tag (gtag.js) - Google Analytics