java 并发包 锁_Java8并发包源码分析:重入锁ReentrantLock和Condition实现原理

一、synchronized同步锁

synchronized的用法和实现原理

参考我的另外一篇文章:Java多线程:synchronized同步锁的使用和实现原理

synchronized的不足

synchronized在线程同步的使用方面,优点是使用简单,可以自动加锁和解锁,但是也存在一些不足:

synchronized是阻塞的,不支持非阻塞,中断和超时退出特性;

synchronized是互斥锁,不支持多个线程对资源的共享访问,如多个读线程进行并发读;

当多个方法共享多个monitor时,要注意使用synchronized加锁的顺序,否则容易产生死锁;

synchronized只支持基于monitor这个对象来进行线程之间条件化通信,即多个线程只能基于一个monitor的wait,notify,notifyAll来进行线程之间的通信,不够灵活,如读写线程之间无法区分;同时由于只有monitor对象的一个等待队列,故所有等待线程均处于该等待队列中,无法区分出是生产者线程还是消费者线程,所以当需要唤醒某个线程时,需要调用notifyAll来唤醒所有线程,否则如果使用notify则可能会出现假死,如本来想唤醒生产者线程,而notify了一个消费者线程,则此时系统就出现“假死”了;

synchronized基于操作系统的Metux Lock来实现,线程之间的切换需要进行上下文切换,成本较高,性能较低。

所以为了解决以上问题,在JDK1.5中提供了Lock和Condition接口来实现synchronized和监视器monitor的功能,核心实现为ReentrantLock。

二、ReentrantLock:可重入锁

设计目的

ReentrantLock也是一个可重入的互斥锁,跟synchronized提供一样的线程同步功能,但是比synchronized更加灵活,即优化了以上所说的synchronized的不足。

ReentrantLock是基于AQS来实现线程同步功能的:

AQS提供了线程等待队列的实现,ReentrantLock自定义线程同步状态state来实现互斥锁的功能即可,即state等于0,表示当前没有任何线程占用这个锁,state大于0,表示当前存在线程占用这个锁,且该占用线程每访问一个使用该锁同步的方法,则state递增1,实现可重入,这个实现逻辑是跟synchronized一样的;

AQS是通过使用自旋和UNSAFE提供的CAS硬件级别的原子操作来对线程等待队列进行增删节点来实现线程的切换的,整个过程为无锁操作,即不需要依赖于操作系统的Metux Lock来实现,故不需要进行线程上下文切换,提高了性能。

在使用方面,与synchronized的自动加锁解锁不同的是,ReentrantLock是需要在应用代码中显式进行加锁和解锁操作的,通常需要结合try-finally来实现,避免异常是无法解锁,如下:

b1335d10745b5686373def70de652179.png

实现

ReentrantLock基于AQS实现,故需要使用一个内部类来实现AQS接口,提供Syn同步锁的功能。在AQS实现类中,主要是需要实现tryAcquire方法定义是否可以成功获取锁,实现tryRelease方法定义释放锁。

对于获取锁tryAcquire,ReentrantLock提供了公平和非公平锁两个实现,默认为非公平锁,公平的含义是根据线程请求获取锁的先后顺序来获取锁,即利用了FIFO队列的特性;非公平的含义是每个请求获取锁的线程在需要锁时,先请求一下是否可以获取锁,如果无法获取,则再放入FIFO队列中。

1.请求获取锁tryAcquire

公平版本:

dbc1bb778aad36fc5ce7c66071cca219.png

非公平版本:

eef515a7acc79615ae28ec1ad2b9a4cc.png

2.释放锁tryRelease

在基类Sync中定义,Sync继承AbstractQueuedSynchronizer:递减state直到0

21b52092e5cd6bd4f35b46188008c014.png

3.各个版本的lock加锁

1.阻塞加锁直到获取锁为止,与synchronized语义一样,不支持中断、超时:

d1d47461ec00f26acb347e06b77f0adb.png

2.阻塞可中断版本:

public void lockInterruptibly() throws InterruptedException {

sync.acquireInterruptibly(1);

}

3.非阻塞版本:非阻塞,非公平,即使使用的是公平锁,能获取锁则返回true,否则返回false。

public boolean tryLock() {

return sync.nonfairTryAcquire(1);

}

4.阻塞可超时版本:阻塞指定时间,若在该指定时间到达之后,还没获取锁,则返回false:

public boolean tryLock(long timeout, TimeUnit unit)

throws InterruptedException {

return sync.tryAcquireNanos(1, unit.toNanos(timeout));

}

三、基于Condition实现生产者消费者模型

Condition在Lock体系设计中,用于实现与synchronized中monitor对象所提供的wait,notify,notifyAll相同的语义,对应的方法分别为await,signal,signalAll。在此基础上进行的优化是:一个Lock可以对应多个Condition,每个Condition对应一个条件化线程等待队列,而在synchronized中只能使用monitor这一个Condition。

一个Lock支持多个Condition的好处是:可以将等待线程进行分类,即每个Condition对应一个条件化线程等待队列,而不是全部放在一个条件化线程等待队列,这样每个Condition在条件满足时,可以调用signal或者signalAll来通知该Condition对应的条件化线程等待队列的线程,而不是所有线程:

这样可以在一定程度上优化性能,特别是signalAll,只需通知对应的条件化线程等待队列即可,让这个线程子集去竞争Lock锁,其他Condition的条件化等待队列中的线程继续休眠;

其次对于signal的调用,可以“精确”通知到该Condition对应的条件化线程等待队列中的一个线程,从而避免了在synchronized中的可能出现“假死”的问题:如在生产者消费者模型中,当生产者往数据队列放入数据后,基于Condition的实现中,可以通知和唤醒消费者线程等待队列的一个线程去数据队列读取数据了,而在synchronized的实现中,由于没有对线程进行区分,故可能通知到线程等待队列的一个生产者线程,如果此时数据队列满了,则该生产者线程被唤醒后发现数据队列还是满了,则继续休眠,此后则没有生产者线程来通知消费者线程消费数据了,整个生产者消费者体系就“假死”了,即生产者无法填充数据,消费者不知道有数据可读继续休眠,所以在synchronized中通常需要使用notifyAll来避免这种情况发生,唤醒所有线程去竞争锁。

所以有了Condition之后,只需要调用condition的signal就看准确唤醒对应某个线程,如生产者线程或者消费者线程,而不需要调用signalAll方法,像synchronized一样调用monitor对象的notifyAll唤醒所有线程,从而提高了性能。

1.await等待的实现

synchronized的wait的语义为:线程占有锁,发现条件不满足,释放锁,线程阻塞休眠,进入条件化等待队列,等待其他线程notify唤醒。

Lock的await的调用也是跟synchronized的wait一样,首先对应的线程需要获取Lock锁进入同步代码,即如果方法没有先调用如lock.lock()获取锁的情况下,调用await了,则会抛IllegalMonitorStateException异常。

await对synchronized的wait的语义实现如下:将当前线程放入条件化等待队列,然后释放锁,在while循环内阻塞休眠,直到被放到AQS同步队列了,这时说明条件满足了,可以去竞争获取锁了,通过调用acquireQueued去竞争获取锁。如果获取锁成功了,则真正从await返回。

11b414a0359756c4af6294b0fbf8b347.png

在应用代码中,await通常需要在while循环中检查条件是否满足,只有对应线程被唤醒,获取锁成功,然后再在while循环检查条件是否满足,如果满足,则继续执行,因为此时只有当前线程占有锁,不会出现并发修改导致条件不满足,如下为LinkedBlockingQueue的put的实现:

541ad4913c1a988ba118e7c240c998ba.png

2.各个版本的await等待条件满足

1.可中断阻塞等待

public final void await() throws InterruptedException

2.可中断,可超时阻塞等待:分别为基于纳秒,指定日期,自定义时间单位的版本

public final long awaitNanos(long nanosTimeout)

throws InterruptedException

public final boolean awaitUntil(Date deadline) throws InterruptedException

public final boolean await(long time, TimeUnit unit) throws InterruptedException

3. signal通知的实现

signal主要是当前占有锁正在执行的线程,在条件满足时,通知和唤醒该Condition对应的条件化等待队列的一个线程,让该线程去竞争获取锁,然后继续执行。

Condition的signal实现主要是将Condition对应的条件化等待队列的头结点移到到AQS的同步队列中,具体为移动到同步队列的尾部,这样这个节点对应的线程就可以去竞争锁了。如下:

049cad636831c5f8ec81e90e7653d43f.png

在使用方面,signal不需要在while循环中,因为调用signal的线程是当前占有锁,正在执行的线程。

四、为什么await,signal,signalAll需要在获取lock锁的前提下调用?

与monitor对象的wait,notify,notifyAll需要在synchronized同步的方法或者方法块内执行一样,Condition的await,signal,signalAll需要在获取lock锁的前提下调用,否则会抛IllegalMonitorStateException一次:因为Condition的条件化等待队列中的线程在唤醒时,是被移动到Lock的同步队列中,然后与其他同步队列中的线程一样竞争获取Lock锁,故需要在获取lock锁的前提下才能调用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值