LockSupport

LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,而LockSupport也成为构建同步组件的基础工具。

LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread)方法来唤醒一个被阻塞的线程。Park有停车的意思,假设线程为车辆,那么park方法代表着停车,而unpark方法则是指车辆启动离开,方法如下:

在Java 6中,LockSupport增加了park(Object blocker)、parkNanos(Object blocker,long nanos) 和 parkUntil(Object blocker,long deadline)3个方法,用于实现阻塞当前线程的功能,其中参数 blocker 是用来标识当前线程在等待的对象(以下称为阻塞对象),该对象主要用于问题排查和系统监控。

Condition

简单介绍

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。

Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。

接口与实例

Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。

Condition的使用方式比较简单,需要注意在调用方法前获取锁:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {lock.lock();try {condition.await();} finally {lock.unlock();}
}
public void conditionSignal() throws InterruptedException {lock.lock();try {condition.signal();} finally {lock.unlock();}
}

一般都会将Condition对象作为成员变量。当调用await()方法后,当前线程会释放锁并在此等待,而其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才从await()方法返回,并且在返回前已经获取了锁。

下面通过一个有界队列的示例来深入了解Condition的使用方式。有界队列是一种特殊的队列,当队列为空时,队列的获取操作将会阻塞获取线程,直到队列中有新增元素,当队列已满时,队列的插入操作将会阻塞插入线程,直到队列出现“空位”:

public class BoundedQueue<T> {private Object[] items;private int addIndex, removeIndex, count;private Lock lock = new ReentrantLock();private Condition notFull = lock.newCondition();private Condition notEmpty = lock.newCondition();public BoundedQueue(int length) {items = new Object[length];}public void add(T t) throws InterruptedException {lock.lock();try {while (count == items.length) {notFull.await();}items[addIndex] = t;if (++addIndex == items.length) {addIndex = 0;}count++;notEmpty.signal();} finally {lock.unlock();}}public T remove() throws InterruptedException {lock.lock();try {while (count == 0) {notEmpty.await();}Object x = items[removeIndex];if (++removeIndex == items.length) {removeIndex = 0;}count--;notFull.signal();return (T) x;} finally {lock.unlock();}}
}

在添加和删除方法中使用while循环而非if判断,目的是防止过早或意外的通知,只有条件符合才能够退出循环。

实现分析

ConditionObject是同步器AbstractQueuedSynchronizer的内部类,因为Condition的操作需要获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象都包含着一个队列(以下称为等待队列),该队列是Condition对象实现等待/通知功能的关键。

1. 等待队列

等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程就是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。

一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾节点(lastWaiter)。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列,等待队列的基本结构如图:

如图所示,Condition拥有首尾节点的引用,而新增节点只需要将原有的尾节点nextWaiter 指向它,并且更新尾节点即可。上述节点引用更新的过程并没有使用CAS保证,原因在于调用 await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的。

在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的 Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列,其对应关系如图所示:

如图所示,Condition的实现是同步器的内部类,因此每个Condition实例都能够访问同步器提供的方法,相当于每个Condition都拥有所属同步器的引用。

2. 等待

调用Condition的await()方法(或者以await开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从await()方法返回时,当前线程一定获取了Condition相关联的锁。

如果从队列(同步队列和等待队列)的角度看await()方法,当调用await()方法时,相当于同步队列的首节点(获取了锁的节点)移动到Condition的等待队列中。

Condition 的 await() 方法:

public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 当前线程加入等待队列Node node = addConditionWaiter();// 释放同步状态,也就是释放锁int savedState = fullyRelease(node);int interruptMode = 0;while (!isOnSyncQueue(node)) {LockSupport.park(this);if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);
}

调用该方法的线程成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造成节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态。

当等待队列中的节点被唤醒,则唤醒节点的线程开始尝试获取同步状态。如果不是通过其他线程调用Condition.signal()方法唤醒,而是对等待线程进行中断,则会抛出 InterruptedException。

3. 通知

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。

Condition 的 signal() 方法:

public final void signal() {if (!isHeldExclusively())throw new IllegalMonitorStateException();Node first = firstWaiter;if (first != null)doSignal(first);
}

调用该方法的前置条件是当前线程必须获取了锁,可以看到signal()方法进行了isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。

通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。当节点移动到同步队列后,当前线程再使用LockSupport唤醒该节点的线程。

被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。

成功获取同步状态(或者说锁)之后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功地获取了锁。

Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。


– 来自《Java并发编程的艺术》总结

Locksupport 与 Condition相关推荐

  1. Java高并发编程(七):读写锁、LockSupport、Condition

    读写锁定义:读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读 线程和其他写线程均被阻塞.读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很 ...

  2. java后台面试题整理

    java基础 Arrays.sort实现原理和Collection实现原理 foreach和while的区别(编译之后) 线程池的种类,区别和使用场景 分析线程池的实现原理和线程的调度过程 线程池如何 ...

  3. 阿里巴巴2020首发136道Java高级岗面试题(含答案)

    原文地址 java基础 Arrays.sort实现原理和Collection实现原理 foreach和while的区别(编译之后) 线程池的种类,区别和使用场景 分析线程池的实现原理和线程的调度过程 ...

  4. Java并发编程的艺术(推荐指数:☆☆☆☆☆☆)

    文章目录 Java并发编程的艺术(推荐指数:☆☆☆☆☆☆) 并发编程的挑战 Java并发机制的底层实现原理 Volatile的应用 实现原理 synchronized的实现原理与应用 对象头 锁详解 ...

  5. 从心灰意冷到自学Java3个月顺利拿到offer,多亏这份文档

    跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽.切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来(期间也没有准备充分),到底是因为技术原因(影响自己的发展,偏移自己规划的 ...

  6. 逆风而行!从心灰意冷到拿到满意的Java开发Offer,多亏这份文档

    引言 又是一年跳槽季,在疫情的影响下,今年的金九银十冷清不少.但无论如何,2021年招聘市场已经显示出了一个清晰的趋势,java开发岗面试越来越难,需求越来越少!也更增加了游戏的"难度系数& ...

  7. 多线程-使用大全 基础使用 / 锁 / 线程池 / 原子类 / 并发包 / CAS / AQS (2022版)

    一.多线程描述 1.什么是cpu CPU的中文名称是中央处理器,是进行逻辑运算用的主要由运算器.控制器.寄存器三部分组成, 运算器:从字面意思看就是运算就是起着运算的作用, 控制器:就是负责发出cpu ...

  8. 不愧是阿里,扣的真细。

    铜三铁四已经过去了,今天的行情虽然没有以前好,但是相比去年来说也算是好了一些了.有一些人已经在这个招聘季拿到了不错的Offer了. 今天给大家分享一份面经,今天这位朋友的背景是Java五年本,2023 ...

  9. Thread.sleep() / Object.wait() / Condition.await() / LockSupport.park() / LockSupport.unpark() 区别

    转自:https://www.cnblogs.com/tong-yuan/p/11768904.html Thread.sleep()和Object.wait()的区别 首先,我们先来看看Thread ...

最新文章

  1. ReadWriteLock
  2. [更新中]Lucene.net,中文分词技术 ICTCLAS研究
  3. Java为什么需要保留基本数据类型
  4. 设计模式复习-职责链模式
  5. js 为false的几种情况
  6. 1分钟了解CDN内容分发技术
  7. cmd命令将web项目打成jar包_2020全网首发!JDK14之jpackage命令尝鲜
  8. Jenkins-Gitlab配置方法
  9. 第3步 (请先看第2步再看第3步) 新建完spring+springmvc+mybatis项目 需要推送gitee仓库进行管理 巨详细
  10. 【Pytorch神经网络实战案例】26 MaskR-CNN内置模型实现目标检测
  11. 【2017年第2期】专题:大数据管理与分析
  12. Python程序中各函数间调用关系分析与可视化
  13. MFC学习——下检测计算机是否联网
  14. ffplay常用命令
  15. VMware卸载后再安装时网络连接处没有虚拟网卡
  16. Java将JSON对象或JSON数组转list对象
  17. directadmin php5.6,directadmin教程
  18. Java 居民身份证号校验工具类
  19. 写给想做互联网产品经理的师弟师妹们一些话
  20. Main.obj : error LNK2019: 无法解析的外部符号 _Direct3DCreate9@4,该符号在函数 long __cdecl InitD3D(struct HWND__ *)

热门文章

  1. 图片像素、尺寸、位深度、图像色深
  2. 苹果iPhone微信分身版如何安装
  3. Spring5解决Log4jConfigListener的问题
  4. 信息安全-攻击-XSRF:XSRF/CSRF 攻击
  5. 【Springboot + Vue 视频播放web项目】解决视频播放只有声音没有画面
  6. 2023年全国最新保安员精选真题及答案28
  7. Weave常见问题集合
  8. 客户询问产品最低价,客服该如何回复?
  9. NAT反向代理技术的实现(外网访问内网)
  10. 中国式教育-虎妈猫爸给我的启发