多线程-ReentrantLock(可重入锁)与synchronized比较

20 篇文章 0 订阅
17 篇文章 0 订阅
本文深入探讨了ReentrantLock与synchronized的区别和联系,包括可重入性、中断响应、锁限时操作和公平性。ReentrantLock提供更高的灵活性,如可中断和限时获取锁,以及配合Condition实现线程间通信。它还支持公平锁,但默认为非公平,以提高性能。在选择使用时,除非需要特定功能,否则通常推荐优先使用synchronized,因为它由JVM原生支持且易于使用。
摘要由CSDN通过智能技术生成
可重入锁新特征:
  • 可中断响应;
  • 锁限时操作;
  • 公平锁;
  • 非公平锁;
  • 结合Condition使用;
继承关系:

在这里插入图片描述
Lock定义了锁的接口规范。
ReentrantLock实现了Lock接口。
AbstractQueuedSynchronizer中以队列的形式实现线程之间的同步。
ReentrantLock的方法都依赖于AbstractQueuedSynchronizer的实现。
sync是在ReentrantLock的构造函数中实现的,其中fair参数的不同可实现公平性锁和非公平性锁。由于在锁释放的阶段,锁处于无线程占有的状态,此时其它线程和在队列中等待的线程都可以抢占改锁,从而出现公平性锁和非公平锁的区别。

ReentrantLock是如何实现锁资源的。

ReentrantLock的锁资源以state状态描述,利用CAS则实现对锁资源的抢占,并通过一个CLH队列阻塞所有的竞争线程,在后续则逐个唤醒等待中的竞争线程。ReentrantLock继承AQS(AbstractQueueSynchronizer的缩写)完全从代码层面实现了java的同步机制,相对于synchronized,更容易实现对各类锁的扩展。同时,AQS中的Condition配合ReentrantLock使用,实现了线程间通信(wait/notify)的功能。

中断响应:

对synchronized快来说,要么获取到锁执行,要么持续等待。而重入锁的中断响应功能就合理地避免了这样地情况。比如,一个正在等待获取锁地线程被“告知”无序继续等待下去,就可以停止工作了。

  • LockInterruptibly(): 可中断地获取锁,和lock的不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程。
  • isHeldByCurrentThread(): 获取当前线程是否保持此锁定。
锁申请等待限时:
  • tryLock(): 尝试非阻塞的获取锁,该方法调用后立即返回,如果获取到则返回true,否则返回false
  • ttyLock(long timeout,TimeUtil unit) throws InterruptedExcepton: 超时的获取锁,有三种返回状态。
    1:当前线程在超时时间内获得了锁,返回true。
    2: 当前线程在超时时间内被中断,抛出中断异常。
    3:超时时间结束仍没有获取锁,返回false。
公平锁:
非公平锁:当锁处于无线程占有的状态时,此时其它线程和队列中等待的线程都可以抢占该锁。
公平性锁:当锁处于无线程占有的状态,在其它线程抢占该锁的时候都需要先进入队列等待。ReentrantLock既可以创建公平性锁,也可以创建非公平性锁。构造函数默认为非公平性锁。
为什么我们不让所有的锁公平呢 ?

在现实中,公平保证的锁是非常健壮的锁,有很大的性能成本。要确保公平所需要的记账(bookkeeping)和同步,就意味着被争夺的公平锁要比不公平锁的吞吐率更低。作为默认设置,应该设置为非公平性锁(false)。除非公平对你的算法至关重要,需要严格按照线程排队的顺序对其进行服务。

ReentrantLock和Synchronized的区别:

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

优点:灵活性更高(力度更小),先行操作,有加锁就有释放锁。
配合Condition使用:

Condition可以代替传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll();

为什么方法名不直接叫wait/notify/notifyAll?

因为Object类中这几个方法是final的,因此是不可以被重写的。

传统线程的通信方式,Condition都可以实现。因为Condition是被绑定在Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
Condition的强大之处在于它可以为多个线程间建立不同的Condition
final Lock lock=new ReentractLock();//锁对象
final Condition notFull=lock.newCondition(); //写线程锁;
final Condition notEmpty=lock.newCondition();  //读线程锁

Reentrant锁意味着什么呢?

简单来说,他有一个与锁有关的获取计数器,如果拥有锁的某个线程再次得到锁,那么计数器+1,然后锁需要被释放两次才能获得真正释放。这模仿了synchronized语义。如果线程进入由线程已经拥有的监视器保护的synchronized块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个synchronized块时,才释放锁。
也就是说,如果线程已经加锁,那么该线程是可以再次进入此锁对象的。进入多少次即加多少次锁,但是如果要释放锁,那么需要解锁多少次。这样才能释放锁。

内部实现原理:

Lock是完全用java代码写成的,在java这个层面是无关JVM的。

ReentrantLock:

Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueueSynchronizer。
AQS会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后续节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查,线程的显式阻塞是通过LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,在进一步,HotSpot在linux中通过调用pthread_mutex_lock(线程互斥锁)函数把线程交给系统内核进行阻塞
与synchronized相同的是,这也是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系。令人疑惑的是为什么采用CLH队列呢? 原生的CLH队列是用于自旋锁,但是并发包作者将其改造为阻塞锁当有线程竞争锁时,该线程会首先尝试获得锁,这对于那些已经在队列中排队的线程来说是不公平的,这也是非公平所的由来,与synchonized实现类似,这样会极大提高吞吐量。如果已经存在Running线程,则新的线程会被追加到队尾,具体则是采用CAS的Lock-Free算法,因为线程并发对Tail调用CAS可能会导致其它线程CAS失败,结果办法是循环CAS直至成功。 面试可以说是底层主要考volatile和CAS操作实现的。

synchronized:

java是用字节码指令来控制程序(不包括热点代码编译形成机器码)。在字节码指令中,存在有sunchronized所包含的代码块,那么就会形成2段流程的执行。其实synchronized映射成字节码指令就是增加两个指令:monitorenter和moniterexit。当一个线程进行进行时遇到monitorenter指令的时候,他会尝试获得锁,如果获得成功,那么锁计数+1(因为synchronized锁时一个可重入锁,所以需要用这个锁计数判断锁的情况),如果没有获得锁,那么阻塞(进入对象锁池)。当它遇到monitorexit时,锁计数器-1,当计数器为0时,那么就释放锁。但是实际上为什么有两个monitorexit呢? synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是发送异常,由虚拟机进行释放。

两者比较:
  • 锁的实现: synchronized是JVM实现的,而ReentrantLock是JDK实现的。
  • 性能:新版本对java对synchronized进行了很多优化,例如自旋锁等,synchronized与ReentrantLock性能大致相同。
  • 等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以放弃等待改为处理别的事情。 ReentrantLock可中断而synchronized不行。
  • 公平锁:公平锁是指在多个线程等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。 synchronized中的锁是非公平的,ReentrantLock默认也是非公平的,但是也是可以设置为公平的。
  • 锁绑定多个条件:一个ReentrantLock可以同时绑定多个Condition对象。而synchronized不行。
synchronized和ReentrantLock使用选择:

除非需要使用ReentrantLock的高级功能,否则优先使用synchronized。这是因为synchronized是JVM实现的一种锁机制JVM原生地支持它,而ReentrantLock并不是所有JDK版本都支持。并且使用synchronized不用担心没有释放锁而导致地死锁问题,因为JVM会确保锁地释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值