可重入锁ReentrantLock及其他显式锁相关问题

1、跟Synchronized相比,可重入锁ReentrantLock其实现原理有什么不同?

其实,锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。
Synchronized通过在对象头中设置标记实现了这一目的,是一种JVM原生的锁实现方式,而ReentrantLock以及所有的基于Lock接口的实现类,都是通过用一个volitile修饰的int型变量,并保证每个线程都能拥有对该int的可见性和原子修改,其本质是基于所谓的AQS框架。

2、 AQS框架是怎么回事儿?

AQS(AbstractQueuedSynchronizer类)是一个用来构建锁和同步器的框架,各种Lock包中的锁(如ReentrantLock、ReadWriteLock),以及其他如Semaphore、CountDownLatch,甚至是早期的FutureTask等,都是基于AQS来构建。
①、AQS在内部定义了一个volatileintstate变量,表示同步状态:当线程调用lock方法时,如果state=0,说明没有任何线程占有共享资源的锁,可以获得锁并将state=1;如果state=1,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。
②、AQS通过Node内部类构成的一个双向链表结构的同步队列,来完成线程获取锁的排队工作,当有线程获取锁失败后,就被添加到队列末尾。
Node类是对要访问同步代码的线程的封装,包含了线程本身及其状态叫waitStatus(有五种不同取值,分别表示是否被阻塞,是否等待唤醒,是否已经被取消等),每个Node结点关联其prev结点和next结点,方便线程释放锁后快速唤醒下一个在等待的线程,是一个FIFO的过程。
Node类有两个常量,SHARED和EXCLUSIVE,分别代表共享模式和独占模式。所谓共享模式是一个锁允许多条线程同时操作(信号量Semaphore就是基于AQS的共享模式实现的),独占模式是同一个时间段只能有一个线程对共享资源进行操作,多余的请求线程需要排队等待(如ReentranLock)。
③、AQS通过内部类ConditionObject构建等待队列(可有多个),当Condition调用wait()方法后,线程将会加入等待队列中,而当Condition调用signal()方法后,线程将从等待队列转移动同步队列中进行锁竞争。
④、AQS和Condition各自维护了不同的队列,在Condition的时候,其实就是两个队列的互相移动。

3、Synchronized和ReentrantLock的异同

ReentrantLock是Lock的实现类,是一个互斥的同步锁。
从功能角度,ReentrantLock比Synchronized的同步操作更精细甚至实现Synchronized没有的高级功能,
如:等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,对处理执行时间非常长的同步块很有用。
带超时的获取锁尝试:在指定的时间范围内获取锁,如果时间到了仍然无法获取则返回。可以判断是否有线程在排队等待获取锁。可以响应中断请求:与Synchronized不同,当获取到锁的线程被中断时,能够响应中断,中断异常将会被抛出,同时锁会被释放。可以实现公平锁。
从锁释放角度,Synchronized在JVM层面上实现的,不但可以通过一些监控工具监控Synchronized的锁定,而且在代码执行出现异常时,JVM会自动释放锁定;但是使用Lock则不行,Lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
从性能角度,Synchronized早期实现比较低效,对比ReentrantLock大多数场景性能都相差较大。
但是在Java6中对其进行了非常多的改进,在竞争不激烈时,Synchronized的性能要优于ReetrantLock;在高竞争情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

4、ReentrantLock是如何实现可重入性的?

ReentrantLock内部自定义了同步器Sync(Sync既实现了AQS,又实现了AOS,而AOS提供了一种互斥锁持有的方式),其实就是加锁的时候通过CAS算法,将线程对象放到一个双向链表中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID是否一样,一样就可重入了。

5、除了ReetrantLock,你还接触过JUC中的哪些并发工具?

通常所说的并发包(JUC)也就是java.util.concurrent及其子包,集中了Java并发的各种基础工具类,具体主要包括几个方面:
提供了CountDownLatch、CyclicBarrier、Semaphore等,比Synchronized更加高级,可以实现更加丰富多线程操作的同步结构。
提供了ConcurrentHashMap、有序的ConcunrrentSkipListMap,或者通过类似快照机制实现线程安全的动态数组CopyOnWriteArrayList等,各种线程安全的容器。提供了ArrayBlockingQueue、SynchorousQueue或针对特定场景的PriorityBlockingQueue等,各种并发队列实现。强大的Executor框架,可以创建各种不同类型的线程池,调度任务运行等。

6、如何让Java的线程彼此同步?你了解过哪些同步器?请分别介绍下。

JUC中的同步器三个主要的成员:CountDownLatch、CyclicBarrier和Semaphore,通过它们可以方便地实现很多线程之间协作的功能。
①、CountDownLatch叫倒计数,允许一个或多个线程等待某些操作完成。用法:CountDownLatch构造方法指明计数数量,被等待线程调用countDown将计数器减1,等待线程使用await进行线程等待。
②、CyclicBarrier叫循环栅栏,它实现让一组线程等待至某个状态之后再全部同时执行,而且当所有等待线程被释放后,CyclicBarrier可以被重复使用。CyclicBarrier的典型应用场景是用来等待并发线程结束。
CyclicBarrier的主要方法是await(),await()每被调用一次,计数便会减少1,并阻塞住当前线程。当计数减至0时,阻塞解除,所有在此CyclicBarrier上面阻塞的线程开始运行。在这之后,如果再次调用await(),计数就又会变成N-1,新一轮重新开始,这便是Cyclic的含义所在。CyclicBarrier.await()带有返回值,用来表示当前线程是第几个到达这个Barrier的线程。
③、Semaphore,Java版本的信号量实现,用于控制同时访问的线程个数,来达到限制通用资源访问的目的,其原理是通过acquire()获取一个许可,如果没有就等待,而release()释放一个许可。如果Semaphore的数值被初始化为1,那么一个线程就可以通过acquire进入互斥状态,本质上和互斥锁是非常相似的。但是区别也非常明显,比如互斥锁是有持有者的,而对于Semaphore这种计数器结构,虽然有类似功能,但其实不存在真正意义的持有者,除非我们进行扩展包装。

7、CyclicBarrier和CountDownLatch看起来很相似,请对比下呢?

它们的行为有一定相似度,区别主要在于:
•CountDownLatch是不可以重置的,所以无法重用,CyclicBarrier没有这种限制,可以重用。
•CountDownLatch的基本操作组合是countDown/await,调用await的线程阻塞等待countDown足够的次数,不管你是在一个线程还是多个线程里countDown,只要次数足够即可。CyclicBarrier的基本操作组合就是await,当所有的伙伴都调用了await,才会继续进行任务,并自动进行重置。
CountDownLatch目的是让一个线程等待其他N个线程达到某个条件后,自己再去做某个事(通过CyclicBarrier的第二个构造方法publicCyclicBarrier(intparties,RunnablebarrierAction),在新线程里做事可以达到同样的效果)。而CyclicBarrier的目的是让N多线程互相等待直到所有的都达到某个状态,然后这N个线程再继续执行各自后续(通过CountDownLatch在某些场合也能完成类似的效果)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

软软的铲屎官

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值