- 浏览: 31551 次
- 性别:
- 来自: 宁波
文章分类
最新评论
-
zpd00001:
大道理,刚毕业的菜鸟们是不懂的- -!!
毕业后五年之内将决定你的一生 人生与励志 -
li_47195:
看完后我沉默很久..所谓“优秀生”?..一篇转载,献给所有和我一样迷茫没有目标的人
重入锁(ReentrantLock)是一种递归无阻塞的同步机制。以前一直认为它是synchronized的简单替代,而且实现机制也不相差太远。不过最近实践过程中发现它们之间还是有着天壤之别。
以下是官方说明:一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
它提供了lock()方法:
如果该锁定没有被另一个线程保持,则获取该锁定并立即返回,将锁定的保持计数设置为 1。
如果当前线程已经保持该锁定,则将保持计数加 1,并且该方法立即返回。
如果该锁定被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁定之前,该线程将一直处于休眠状态,此时锁定保持计数被设置为 1。
最近在研究Java concurrent中关于任务调度的实现时,读了延迟队列DelayQueue的一些代码,比如take()。该方法的主要功能是从优先队列(PriorityQueue)取出一个最应该执行的任务(最优值),如果该任务的预订执行时间未到,则需要wait这段时间差。反之,如果时间到了,则返回该任务。而offer()方法是将一个任务添加到该队列中。
后来产生了一个疑问:如果最应该执行的任务是一个小时后执行的,而此时需要提交一个10秒后执行的任务,会出现什么状况?还是先看看take()的源代码:
而以下是offer()的源代码:
如代码所示,take()和offer()都是lock了重入锁。如果按照synchronized的思维(使用诸如synchronized(obj)的方法),这两个方法是互斥的。回到刚才的疑问,take()方法需要等待1个小时才能返回,而offer()需要马上提交一个10秒后运行的任务,会不会一直等待take()返回后才能提交呢?答案是否定的,通过编写验证代码也说明了这一点。这让我对重入锁有了更大的兴趣,它确实是一个无阻塞的锁。
下面的代码也许能说明问题:运行了4个线程,每一次运行前打印lock的当前状态。运行后都要等待5秒钟。
这是它的输出:
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了锁。
以下是官方说明:一个可重入的互斥锁定 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了锁。
发表评论
-
【转】PC安卓模拟器PANIC: Could not open:C:\Documents and Settings\Administrator\.android
2013-03-13 23:43 842在初次运行Android程序的 ... -
Spring 设置支态定时任务
2012-11-27 12:27 652什么是动态定时任务:是由客户制定生成的,服务端只知道 ... -
Java TCP/IP Socket 编程 笔记(四)—发送和接收数据
2012-10-29 20:17 19291.TCP/IP协议要求信息必须在块(chunk)中发送和接收 ... -
Java TCP/IP Socket 编程 笔记(三)—UDP的例子
2012-10-29 19:53 9151.UDP套接字与TCP套接字 ... -
Java TCP/IP Socket 编程 笔记(二)—TCP的例子
2012-10-29 19:49 7591.InetAddress类和SocketAddress用于 ... -
Java TCP/IP Socket 编程 笔记(一)—基本概念
2012-10-29 19:42 870一些概念: 通信信道(communication c ... -
java并发编程不得不知道的几件事(转载)
2012-10-29 19:31 647多线程编 ... -
Web.XML 配置详解
2012-09-07 11:56 580每一个站的WEB-INF下都有一个web.xml的设定文件, ... -
java 多线程编程需要注意的23条
2012-08-13 15:56 5751.多线程中有主内存和 ... -
感受Java中的多线程设计
2012-08-13 15:49 659我就不说最初那个单核CPU时代了,我们从多进程编程开始讲。 ... -
排序算法(JAVA)(二)归并排序,堆排序,桶式排序,基数排序
2012-07-11 09:05 548六 归并排序 算法思想是每次把待排序列分成两部分,分别对这两部 ... -
排序算法(JAVA)(一)插入排序,冒泡排序,选择排序,Shell,快速排序
2012-07-11 08:59 727为了便于管理,先引入个基础类: package algor ... -
理解ThreadLocal
2012-04-28 16:05 0ThreadLocal是什么 早在JDK ... -
Java反射机制
2011-12-31 13:16 672http://www.cnblogs.com/Quincy/ ... -
Java5.0多线程编程
2011-11-22 16:00 710[size=large] Lock接口 ReentrantL ... -
Java Map遍历的方法
2011-11-09 19:41 836第一种:利用entryset遍历 Map map = ne ... -
Java常见异常汇总
2011-11-09 19:27 670转自于: http://www.javaask.com/jav ... -
java io/流
2011-11-09 19:20 484[转]JAVA IO流 http://www.blogjava ... -
Java:使用synchronized和Lock对象获取对象锁
2011-11-07 12:43 557原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 ... -
Java线程:创建与启动
2011-10-31 13:50 602一、定义线程 1、扩展 ...
相关推荐
`ReentrantLock`是一种可重入的互斥锁,支持公平和非公平两种模式。它内部有一个`Sync`类,继承自`AbstractQueuedSynchronizer`,并且有两个子类分别实现了公平和非公平锁的逻辑。 - **非公平锁** (`NonfairSync`):...
ReentrantLock 的特点是可以重入,即一个线程可以多次获得锁,每次获得锁时,锁的计数器都会增加,直到锁被释放时,计数器才会减少。 在上面的代码中,我们使用 ReentrantLock 来实现线程同步。在 UseReentrantLock...
Lock接口有三种实现类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。Lock锁必须被显式地创建、锁定和释放,以确保锁最终一定会被释放。 在使用Lock锁...
ReentrantLock是Lock接口的一个实现,具有可重入性,即一个线程可以进入已经由该线程持有的锁。 7. ThreadLocal:为每个线程提供独立的变量副本,避免了线程之间的数据冲突。每个线程都拥有自己的ThreadLocal变量,...
而Lock接口提供更细粒度的锁控制,如ReentrantLock可重入锁。 此外,死锁、活锁和饥饿现象也是面试热点。死锁是指两个或多个线程相互等待对方释放资源,导致都无法继续执行;活锁则是线程不断尝试获取资源但都失败...
例如,在并发栈的例子中,可以使用ReentrantLock 锁机制来保证线程安全,避免ABA问题的出现。 ABA问题是Java并发编程中的一种常见问题,需要开发者对其进行认真对待和处理,以避免程序的不正确执行和数据的不一致。...
再来看看 get 函数: public V get(Object key) { if (key == null) return getForNullKey(); Entry,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } final Entry,V> getEntry...
3. **Lock接口和实现**:如`ReentrantLock`,提供了更细粒度的锁控制和更多的同步机制。 在实际开发中,根据需求选择合适的多线程实现方式,并合理使用同步机制,可以有效地提高程序的并发性能和安全性。
第5章深入介绍了Java并发包中与锁相关的API和组件,如ReentrantLock、ReadWriteLock等,以及它们的使用方式和实现细节。同时,书中也探讨了锁优化技术,如锁粗化、锁消除等,这些都是提高并发程序性能的关键技术点。...
17.3.2 ReentrantLock锁的具体使用 387 17.3.3 ReadWriteLock接口与ReentrantReadWriteLock类简介 390 17.3.4 ReentrantReadWriteLock读/写锁的具体使用 391 17.4 信号量的使用 393 17.4.1 Semaphore类简介...