您当前的位置:首页 > 互联网教程

java reentrantlock用在什么地方

发布时间:2025-05-23 20:38:16    发布人:远客网络

java reentrantlock用在什么地方

一、java reentrantlock用在什么地方

1.ReentrantLock类实现了 Lock,它拥有与 synchronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM可以花更少的时候来调度线程,把更多时间用在执行线程上。)

2.reentrant锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized块时,才释放锁。

3.lock必须在 finally块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM将确保锁会获得自动释放。

二、JAVA多线程之AQS的实现重入锁ReentrantLock

在JAVA体系当中锁是实现多线程中同步的一个重要机制,JAVA的锁有synchronized这个是JVM层面实现的锁,JAVA实现的锁同步有:Lock、ReadWriteLock、ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier,包括上一篇中讲到的ThreadPoolExecutor中Worker执行用到锁,也是来自于本文所讲的AQS实现,所以要理解JAVA的锁,除了JVM里实现的外,只要搞懂AQS的就可以了,话不多说,赶紧开始吧。

下面代码中可以看出AQS的类继承关系比较简单,就是继承AbstractOwnableSynchronizer类并实现了Serializable接口,

AbstractOwnableSynchronizer类功能比较简单,主要是设置锁的排他线程,设置排它线程表示是排它锁,没有设置表示共享锁。

下面是AQS中比较重要的是三个成员变量,head是等待队列中头节点,tail是等待队列中尾结点,state是同步的状态值。

加下来看下NOde类的数据结构的定义:等待队列中waitStatus(等待状态)的的枚举:1.CANCELLED(1)这个节点是由于超时或者中断。2.SIGNAL(-1)这个节点的后继是被阻塞的,所以当前Node取消或者释放锁时,需要unpark去唤醒后继节点。3.CONDITION(-2)这个节点是在条件等待队列。4.PROPAGATE(-3)就是对当前的节点操作传播到其他节点,比如doReleaseShared共享模式的释放锁就是传播后续节点。5.0不是上面的任何一种状态

首先看下ReentrantLock的获取锁的过程,它的构造函数初始化一个NonfairSync,也就是说这个重入锁是非公平锁,

从这里看到ReentrantLoc默认创建的非公平锁,调用lock会直接进行CAS尝试抢锁(这里state=0代表无所,1代表有一个线程已经抢到了锁),如果没有抢到锁,则正常调用AQS的acquire方法默认参数1去获取锁,

那么接下来重点看下AQS的acquire方法获取锁的逻辑1tryAcquire尝试获取锁,2.如果获取失败,则加入一个排他的Node到等待队列

非公平锁,一开始就会尝试抢锁,而不管之前等待队列中是否有等待的抢锁的线程,

此时state值为0,则直接通过CAS将其值修改成acquires(ReentrentLock是1),并设置排他线程为它自己,这里说明ReentrantLock的加的锁是排它锁。

当排它线程就是当前线程,则表示当前线程已经获取锁,这个时候代表是偏向锁,增加acquires值,并赋值给state变量,表示同一线程同时同一把锁的次数,

上面的tryAcquire这一步尝试抢锁失败后,然后执行addWaiter(Node.EXCLUSIVE),新建一个排它模式的Node并加入等待队列。

下图是上面node加入到队列尾部的过程,这里注意双向队列首先是新增的node的prev指针指向原先的tail,至于为什么在下面会有分析

下面enq就是通过for的无限循环中再以CAS方式加入了新建的Node,这样保证新建的Node一定加入到等待队列中。

将新建的尾部节点加入等待队列后,然后执行acquireQueued方法,参数是上面加入到等待队列中的Node以及args(tryAcquire的acquire变量),这个方法的逻辑是处理新的Node加入等待队列,然后尝试从队列中唤醒之前的等待队列中的线程去运行,

shouldParkAfterFailedAcquire是获取锁后失败后,需要park阻塞等待锁,

如果获取锁失败,acquireQueued方法中failed为true时,调用cancelAcquire取消获取锁,

唤醒前驱节点的逻辑是从tail节点从尾部往前,通过找到链表上一个waiteStatus小于0的节点(即不是取消状态的线程),然后执行LockSupport的unpark唤醒等待锁的线程,这个就是上面提到双向链表是两个指针,分两步首先是加的prev指针,所以得从从后往前遍历就能保证遍历到全部节点

接来下我们看下如何释放锁,他一样是来自于AQS父类的release方法,

将state变量减去release后,如果c=0,说明已经没有无锁了,那么设置排它线程为NULL,并设置state变量为0

本文主要对于ReentrantLock的加排它锁和释放排它锁的过程做了一次详细的分析,主要是围绕state变量还有CLH的双端队列展开,以及线程阻塞(LockSupport.park)和唤醒(LockSupport.park),最后ConditionObject没有分析到,后面再写。

三、java如何实现线程安全,synchronized和lock的区别,可重入锁

一、synchronized和lock的用法区别

synchronized:在需要同步的对象中加入此控制,synchronized在方法上,也在特定代码块中,括号中表示需要锁的对象。

lock:需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。

二、synchronized和lock用途区别

synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候。

某个线程在等待一个锁的控制权的这段时间需要中断

2.需要分开处理一些wait-notify,ReentrantLock里面的Condition应用,能够控制notify哪个线程

3.具有公平锁功能,每个到来的线程都将排队等候