java并发工具类浅析:ReentrantLock、Semaophore、CountDownLatch、CyclicBarrier

AQS

  • AbstractQueuedSynchronizer
  • 下面所有的并发工具类都不同程度的依赖这个抽象类,
  • 功能:
    • 其中主要维护了一个同步等待队列的head节点和tail节点,以及一个表示资源数量的成员变量state
    • 通过大量cas操作保证并发操作的原子性,以及通过unsafe类调用系统底层的park、unpark指令来完成线程阻塞、解除阻塞操作。
    • AQS有个内部类ConditionObject,每个Condition都维护自己的条件等待队列。
    • 只有独占锁类型才能有条件等待队列,如果是共享类型则这个字段永远是个常量。
  • 下面所有并发工具类都时通过修改AQS.state来控制数量的,另外需要等待及阻塞的线程都会放到同步等待队列中。
  • 在独占锁类型中,state表示锁重入的次数,在共享锁类型中,state一般是倒数的,声明时就指定state的数量,每有一个节点取用了一个state值就减1,以此来控制共享锁的数量。

 

ReentrantLock

  • 可重入锁,独占锁
  • 用于保证同一时刻只有一个线程进入锁住的资源,其余的线程都会阻塞等待。
  • 使用:
    • 声明时指定公平锁还是非公平锁。
    • lock(),获取锁,锁住资源,如果当前锁被占有则进入同步等待队列,如果是非公平锁会直接先尝试获取锁,公平锁会先判断等待队列中是否有排队的节点。
    • unlock(),解锁资源,释放锁,其中包含了解除等待队列线程的阻塞的操作,但很有可能被非公平锁抢占了。
    • tryLock(),以非公平锁的姿势尝试获取锁,不管原本这个锁是公平锁还是啥,就是尝试插队获取锁的意思。返回成功或失败。
    • lockInterruptibly(),跟lock()流程差不多,只不过这里一旦发现线程是中断的,就里立马出异常,但是lock()里如果发现线程是中断的,没啥过激反应,还会调用线程的中断方法。
  • 大致原理:
    • 调用lock()之类的方法后,AQS.state加1表示之后的资源被当前线程锁住了,其余线程进来会判断AQS.state是否为0尝试获取锁,失败后进入同步等待队列,然后被阻塞住。(这里公平锁和非公平锁会有不同细节处理)
    • AQS.state不为0还有一种情况,就是当前线程自己又重入了,此时允许这个线程继续获取锁,并把AQS.state加1,表示重入了一次,解锁时也相应的需要解锁两次。
    • 不管是公平锁还是非公平锁,只要进入了同步等待队列相当于都开始排队了,队列中的线程前后顺序是一定的,当然其间有可能会被插队。

 

Condition

  • 条件等待,这基本是ReentrantLock和ReentrantReadWriteLock专用的条件阻塞工具。
  • 用于在ReentrantLock中控制线程阻塞及唤醒
  • 之所以要用专用的是为了线程阻塞的同时能正常释放锁资源而不至于所有线程都阻塞在这。
  • 使用:
    • 通过当前使用的ReentrantLock()对象调用newCondition()方法。
    • 在希望线程阻塞的地方调用condition.await()方法。
    • 在能够唤醒阻塞的线程的时候调用condition.signal()唤醒一个或condition.signalAll()唤醒所有。
  • 大致原理:
    • 每个Condition中维护一个条件等待队列。
    • 当有线程调用await()方法时,将这个线程放到条件等待队列中,同时释放占有的锁资源。
    • 当有线程调用signal()方法时,唤醒条件等待队列中的第一个,加入到AQS的同步等待队列,重新去竞争锁资源。
    • 当有线程条件signal()方法时,唤醒条件等待队列中的所有,加入到AQS的同步等待队列。

 

Semaophore

  • 信号量
  • 用于保证永远都只有指定数量的线程访问被圈定的资源。
  • 使用:
    • 声明时指定数量,还有公平与非公平,默认是非公平的。
    • 每个线程进入时先调用一次acquire()方法取得信号量,后面再调用一次release()释放信号量。
    • acquire() 和 release() 两个方法必须成对存在,即取用信号量和归还信号量动作必须成对存在。
  • 大致原理:
    • 声明时指定的数量就是AQS.state
    • 每次acquire()都把AQS.state减1,减到0时说明没有资源了,就把没获取到资源的线程放到同步等待队列中,然后阻塞住。
    • 每次release()时,都会把AQS.state加1,同时解除同步队列中阻塞的线程去竞争锁,如果成功了会继续解锁队列中后面的,直到竞争不到锁为止。

 

CountDownLatch

  • 计数门闩(我随便起的名)
  • 用于保证多个异步线程在主流程的指定位置执行完成。
  • 使用:
    • 声明时指定数量。
    • 在需要的线程中(最好是finallly中)调用 countDownLatch.countDown()
    • 在主线程需要阻塞的地方调用 countDownLatch.await()
  • 大致原理:
    • 一开始声明的数量就制定了AQS.state,后面每次取用就减1
    • 主线程会在 await() 位置阻塞,直到有指定数量的countDown()方法被调用,
    • 这些countDown()方法理所当然的可以分散在不同的线程中,只要是同一个countDownLatch对象调用的即可。
    • 需要注意的是,只能使用一次,一次之后,countDownLatch中的AQS.state变回0,所有判定基本都相当于直接通过。
    • 注意整个过程中只有主线程会被阻塞,主线程阻塞是为了等待数量达到指定值。
  • 可以说这个门闩是一个最低标准的考核,只要数量达到了最低标准,就可以无视这个东西。
  • 可以视为:用过一次后,这个门闩就被摧毁了。后面再怎么调用也没卵用了。

CyclicBarrier

  • 循环壁垒(翻译过来的名字,也有叫栅栏的)
  • 用于保证 直到指定数量的线程到达各自的指定位置之前,所有的先到达的线程都会在这个指定位置阻塞等待,然后数量足够之时一次性全部解除阻塞。
  • CyclicBarrier跟CountDownLatch不同的地方在于,后者只会阻塞主线程,前者却没有主线程的概念,所以会阻塞所有线程,并且后者只能用一次,前者可以重复使用。
  • 使用:
    • 声明时指定数量
    • 每个线程指定阻塞等待的位置调用 await() 方法,届时每次线程到达这里都会检查是否已有指定数量的线程调用过await()方法,数量不够就阻塞,数量足够就一起解锁全部其他线程。
    • 这个是可以重复使用的,即数量充足的线程到达指定位置后,状态会重置为最开始未使用时的状态。
    • 一旦发现有一个线程是中断的,那所有的线程线程都会抛出异常。
  • 大致原理:
    • 声明时指定的数量会记录下来parties字段,同时将数值传递给count字段,
    • 还可以指定一个run()方法,只有所有线程到达后才会调用,
    • 其内部会指定一个非公平的ReetrantLock(可重入锁)
    • 每次调用await把count字段减1,然后检查是否已经减到0
    • 如果count字段减到0,就尝试唤醒所有阻塞睡眠的线程,失败则一起抛异常
    • 如果count字段没有减到0,则会被阻塞住,这里的阻塞使用的是ReetrantLock中的Condition条件对象进行阻塞的,理所当然的,被阻塞的节点都会进入条件等待队列。而被唤醒后则加入同步等待队列去竞争锁。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值