【Java并发】——lock体系(一)

目录

1.lock与synchronized的区别

1.1 lock补充

2.AQS同步器 (AbstractQueuedSynchronized)

2.1 设计意图

2.2 如何使用AQS实现自定义同步组件

2.3 可重写的方法

2.4 AQS提供的模板方法

3.AQS源码解析

3.1 AQS同步队列的数据结构

3.2 独占与共享


1.lock与synchronized的区别

它虽然失去了synchronized隐式获取锁和释放锁的便捷性,但lock却拥有了释放锁和获取 所得可操作性,可中断的获取锁和超时获取锁等多种synchronnized所不具备的同步特性

以下是lock和synchronized的区别表:

类别synchronizedLock
存在层次Java的关键字,在jvm层面上是一个类
锁的释放1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态无法判断可以判断
锁类型可重入 不可中断 非公平可重入 可判断 可公平(两者皆可)
性能少量同步大量同步

1.1 lock补充

从Lock接口中我们可以看到主要有个方法,这些方法的功能可以看出:

  • lock():获取锁,如果锁被暂用则一直等待

  • unlock():释放锁

  • tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true

  • tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间

  • lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事

通过 以上的解释,大致可以解释在上个部分中“锁类型(lockInterruptibly())”,“锁状态(tryLock())”等问题,还有就是前面子所获取的过程我所写的“大致就是可以尝试获得锁,线程可以不会一直等待”用了“可以”的原因。

 

2.AQS同步器 (AbstractQueuedSynchronized)

什么是同步器?

多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足 xxxx 条件,才能触发线程执行 xxxx 。

这个共同的语义可以称之为同步器。可以认为以上所有的锁机制都可以基于同步器定制来实现的。

我们来看下java.util.concurrent.locks大致结构:

上图中,LOCK的实现类其实都是构建在AbstractQueuedSynchronizer上,为何图中没有用UML线表示呢,这是每个Lock实现类都持有自己内部类Sync的实例,而这个Sync就是继承AbstractQueuedSynchronizer(AQS)。为何要实现不同的Sync呢?这和每种Lock用途相关。另外还有AQS的State机制。

2.1 设计意图

AQS提供给同步组件实现者,为其屏蔽了同步状态的管理,线程排队等 底层操作实现者只需要通过AQS提供的模板方法实现同步组件的语义,lock(同步组件)是面向使用者的,定义了接口,隐藏了实现细节。

 

2.2 如何使用AQS实现自定义同步组件

  1. 重写protected方法,告诉AQS如何判断 当前同步状态获取是否成功或者失败
  2. 同步组件调用AQS的模板方法,实现同步语义。而提供的模板方法又会调用被重写的方法
  3. 实现自定义同步组件时,推荐采用继承AQS的静态内存类

2.3 可重写的方法

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

  不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

  • tryAcquire:独占方式。尝试获取资源,成功则返回true,失败则返回false。不会阻塞
  • tryRelease:独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared:共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared:共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
  • isHeldExclusively:该线程是否正在独占资源。只有用到condition才需要去实现它。

2.4 AQS提供的模板方法

1.独占式获取与释放同步状态

2.共享式获取与释放同步状态

3.查询同步队列中等待线程情况

 AQS提供了独占锁和共享锁必须实现的方法,具有独占锁功能的子类,它必须实现tryAcquire、tryRelease、isHeldExclusively等;共享锁功能的子类,必须实现tryAcquireShared和tryReleaseShared等方法,带有Shared后缀的方法都是支持共享锁加锁的语义。Semaphore是一种共享锁,ReentrantLock是一种独占锁。

独占锁获取锁时,设置节点模式为Node.EXCLUSIVE


public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 共享锁获取锁,节点模式则为Node.SHARED

private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        .....
    }

 

 

3.AQS源码解析

3.1 AQS同步队列的数据结构

带有结点的双向链表实现的队列

3.2 独占与共享

java并发包提供的加锁模式分为独占锁和共享锁,独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁。共享锁,则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。AQS的内部类Node定义了两个常量SHARED和EXCLUSIVE,他们分别标识 AQS队列中等待线程的锁获取模式。

很显然,独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。 java的并发包中提供了ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,或者被一个 写操作访问,但两者不能同时进行。

独占锁

ReentrantLock是AQS独占功能的一个实现,通常的使用方式如下:

reentrantLock.lock();
// do something
reentrantLock.unlock();

ReentrantLock会保证执行do something在同一时间有且只有一个线程获取到锁,其余线程全部挂起,直到该拥有锁的线程释放锁,被挂起的线程被唤醒重新开始竞争锁。

ReentrantLock的加锁全部委托给内部代理类完成,ReentrantLock只是封装了统一的一套API而已,而ReentrantLock又分为公平锁非公平锁

abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
  • 公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序,并依此顺序获得锁,类似于排队吃饭;
  • 非公平锁:每个线程抢占锁的顺序不变,谁运气好,谁就获得锁,和调用lock方法的先后顺序无关,类似后插入。

换句话说,公平锁和非公平锁的唯一的区别是在获取锁的时候是直接去获取锁,还是进入队列排队的问题。

 

共享锁

获取锁的过程:

  1. 当线程调用acquireShared()申请获取锁资源时,如果成功,则进入临界区。
  2. 当获取锁失败时,则创建一个共享类型的节点并进入一个FIFO等待队列,然后被挂起等待唤醒。
  3. 当队列中的等待线程被唤醒以后就重新尝试获取锁资源,如果成功则唤醒后面还在等待的共享节点并把该唤醒事件传递下去,即会依次唤醒在该节点后面的所有共享节点,然后进入临界区,否则继续挂起等待。

释放锁过程:

  1. 当线程调用releaseShared()进行锁资源释放时,如果释放成功,则唤醒队列中等待的节点,如果有的话。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值