前言

JDK版本: 1.8

作用

LockSupport类主要提供了park和unpark两个native方法,用于阻塞和唤醒线程。注释中有这么一段:

这个类是为拥有更高级别抽象的并发类服务的,开发中我们不会用到这个类

既然只是native方法,开发中也用不到,那么还有必要去看么?

了解LockSupport可以帮助我们更好理解并发,而且大家熟悉的并发中最核心的AQS类中也大量的使用了LockSupport,所以还是有必要看一看的,至少熟悉其中的概念。

为什么需要LockSupport

已经知道了这个类就是阻塞唤醒,Object.wait和Object.notify,Thread.suspend和Thread.resume这两对方法也是类似效果,那么还有必要去看么???

Thread.suspend和Thread.resume为什么被弃用

  • suspend将线程挂起,从运行状态阻塞状态,但是并不释放所占用的锁
  • suspend方法至少已经满足互斥,不可剥夺两个死锁的条件了
  • resume将线程解除挂起,从阻塞状态到运行状态,通常是等待其他任务完成, 请求与保持条件也成立了
  • 最后只差 循环等待条件 就死锁了,这实在太危险了,一不小心就容易死锁,而且死锁的问题是很难排查的

Object.wait和Object.notify存在什么问题

  • 不满足条件时我们需要在代码中保证拿到锁才能调用,把线程放到等待队列中
  • notify是从等待池中随机放一个线程出来,当需要唤醒特定线程时,只能notifyAll

LockSupport会有上面的问题么,又有哪些特点呢,让我们进入源码

源码

类声明和属性

package java.util.concurrent.locks;

public class LockSupport {
// 工具类,ban掉构造
private LockSupport() {} private static final sun.misc.Unsafe UNSAFE;
// parkBlocker的内存偏移量
private static final long parkBlockerOffset;
private static final long SEED;
private static final long PROBE;
private static final long SECONDARY;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
// 反射拿到Thread类中的parkBlocker属性,然后获取其在内存中的偏移量
parkBlockerOffset = UNSAFE.objectFieldOffset
(tk.getDeclaredField("parkBlocker"));
SEED = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSeed"));
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
SECONDARY = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomSecondarySeed"));
} catch (Exception ex) { throw new Error(ex); }
} }

park()

// 最简单的方式,但是不推荐
public static void park() {
/**
* 将当前线程挂起,是通过二元信号量,获取许可证实现的,拿到许可证后才执行挂起
* 不是基于对象的监视器锁,所以不需要显示的同步
* 如果超时了,被中断了或者unpark了就会return并且释放许可证
* 需要注意的是和wait一样也会因为JVM内部未知原因return,所以我们如果使用也需要放在循环内
* 第一个参数 flase代表纳秒级别超时控制,此级别下第二个参数timeout为0代表无限等待
* 第一个参数 true代表毫秒级别超时控制,此级别下第二个参数timeout为0会立即返回
*/
UNSAFE.park(false, 0L);
} // 推荐方式,blocker是个辅助对象,用于跟踪许可证的获取,以及定位一些阻塞问题,一般情况park(this)就行
public static void park(Object blocker) {
Thread t = Thread.currentThread();
// 标记对于当前线程t,blocker正在获取许可证,出问题通过getBlocker方法去定位
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
// park操作return了,标记许可证已经释放
setBlocker(t, null);
} private static void setBlocker(Thread t, Object arg) {
// 通过偏移量,把给当前线程t的parkBlocker属性赋值为arg
UNSAFE.putObject(t, parkBlockerOffset, arg);
}

相信大家已经基本了解park操作了,LockSupport还给我们提供了其他功能

// 推荐,纳秒级别timeout后return
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
} // 推荐,毫秒级别timeout后return
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
} // 不推荐,纳秒级别timeout后return
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
} // 不推荐,毫秒级别timeout后return
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}

unpark

public static void unpark(Thread thread) {
if (thread != null)
//释放线程thread的许可证,如果已经是释放状态那就什么都不会发生,因为总共就1个许可,所以unpark可以先于park执行没有任务问题
UNSAFE.unpark(thread);
}

其他方法

// 由于包权限问题从ThreadLocalRandom类中copy过来的,用于生成随机数种子
static final int nextSecondarySeed() {
int r;
Thread t = Thread.currentThread();
if ((r = UNSAFE.getInt(t, SECONDARY)) != 0) {
r ^= r << 13; // xorshift
r ^= r >>> 17;
r ^= r << 5;
}
else if ((r = java.util.concurrent.ThreadLocalRandom.current().nextInt()) == 0)
r = 1; // avoid zero
UNSAFE.putInt(t, SECONDARY, r);
return r;
}

实践

public class LockSupportTest {

    public static void main(String[] args) {
AtomicBoolean flag = new AtomicBoolean(true);
Thread thread = new Thread(() -> {
Thread curr = Thread.currentThread();
System.out.println("线程1 即将被阻塞");
while (flag.get()) {
LockSupport.park(curr);
System.out.println("线程1 复活");
}
System.out.println("线程1 结束使命");
});
thread.start(); new Thread(() -> {
System.out.println("唤醒线程1");
flag.compareAndSet(true, false);
LockSupport.unpark(thread);
}).start();
} /**
* 输出:
* 线程1 即将被阻塞
* 唤醒线程1
* 线程1 复活
* 线程1 结束使命
*/
}

Java读源码之LockSupport的更多相关文章

  1. Java读源码之ReentrantLock

    前言 ReentrantLock 可重入锁,应该是除了 synchronized 关键字外用的最多的线程同步手段了,虽然JVM维护者疯狂优化 synchronized 使其已经拥有了很好的性能.但 R ...

  2. Java读源码之ReentrantLock(2)

    前言 本文是 ReentrantLock 源码的第二篇,第一篇主要介绍了公平锁非公平锁正常的加锁解锁流程,虽然表达能力有限不知道有没有讲清楚,本着不太监的原则,本文填补下第一篇中挖的坑. Java读源 ...

  3. Java读源码之CountDownLatch

    前言 相信大家都挺熟悉 CountDownLatch 的,顾名思义就是一个栅栏,其主要作用是多线程环境下,让多个线程在栅栏门口等待,所有线程到齐后,栅栏打开程序继续执行. 案例 用一个最简单的案例引出 ...

  4. Java读源码之Thread

    前言 JDK版本:1.8 阅读了Object的源码,wait和notify方法与线程联系紧密,而且多线程已经是必备知识,那保持习惯,就从多线程的源头Thread类开始读起吧.由于该类比较长,只读重要部 ...

  5. Java读源码之ThreadLocal

    前言 JDK版本: 1.8 之前在看Thread源码时候看到这么一个属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal实现的是 ...

  6. Java读源码之Object

    前言 JDK版本: 1.8 最近想看看jdk源码提高下技术深度(比较闲),万物皆对象,虽然Object大多native方法但还是很重要的. 源码 package java.lang; /** * Ja ...

  7. java读源码 之 map源码分析(HashMap,图解)一

    ​ 开篇之前,先说几句题外话,写博客也一年多了,一直没找到一种好的输出方式,博客质量其实也不高,很多时候都是赶着写出来的,最近也思考了很多,以后的博客也会更注重质量,同时也尽量写的不那么生硬,能让大家 ...

  8. java读源码 之 queue源码分析(PriorityQueue,附图)

    今天要介绍的是基础容器类(为了与并发容器类区分开来而命名的名字)中的另一个成员--PriorityQueue,它的大名叫做优先级队列,想必即使没有用过也该有所耳闻吧,什么?没..没听过?emmm... ...

  9. java读源码 之 list源码分析(LinkedList)

    文章目录 LinkedList: 继承关系分析: 字段分析: 构造函数分析: 方法分析: LinkedList: 继承关系分析: public class LinkedList<E> ex ...

随机推荐

  1. 【MySQL】为什么SQL会这么慢

    建表 CREATE TABLE `ts_ab` ( `id` int(11) NOT NULL, `a` int(11) DEFAULT NULL, `b` varchar(20) CHARACTER ...

  2. CentOS配置服务开机自启

    本例子以nginx作为举例: CentOS7中 以systemctl 替代了chkconfig 进行服务的管理, 默认在CentOS6 中控制服务开机自启,是在vim /etc/rc.d/rc.loc ...

  3. HTTPS加密协议

    使用JDK自带的keytool工具生成一个证书(keystore文件),其中包含了密钥. a.在命令行输入以下命令:keytool -genkey -alias tbb -keyalg RSA -ke ...

  4. 【深入浅出-JVM】(77):SPI

    概念 Service Provider Interface 规则 在resource/META-INF/services 创建一个以接口全限定名为命名的文件,内容写上实现类的全限定名 接口实现类在cl ...

  5. myeclipse 保存含中文的jsp失败,提示内容含有 ISO-8859-1 不支持的字符

    就是这货,网上说各种设置首选项编码神马的,但我只是临时学一学jsp,装的myeclipse貌似不全,没有他们说的选项,后来发现了解决方案: 出错是因为我的jsp文件是用于在其他jsp中引入的,所以没有 ...

  6. Java位运算实现加减乘除四则运算

    本文是继<一文了解有趣的位运算>的第二篇文章. 我们知道,计算机最基本的操作单元是字节(byte),一个字节由8个位(bit)组成,一个位只能存储一个0或1,其实也就是高低电平.无论多么复 ...

  7. 使用dbutils

    环境准备: 包结构: mysql导出sql脚本: //product CREATE TABLE `product` ( `id` int(10) NOT NULL AUTO_INCREMENT, `n ...

  8. JS基础-全方面掌握继承

    前言 上篇文章详细解析了原型.原型链的相关知识点,这篇文章讲的是和原型链有密切关联的继承,它是前端基础中很重要的一个知识点,它对于代码复用来说非常有用,本篇将详细解析JS中的各种继承方式和优缺点进行, ...

  9. 学习方法分享:为何一年半就能拿到大厂 offer

    毕竟是聊聊曾经,放一张大学课堂上灵光一现,手写的一个我曾经一直使用的网名 前言 原文地址:Nealyang/personalBlog 讲真,的确是运气,才有机会进大厂.也没想到,那篇一年半工作经验试水 ...

  10. 章节十六、10-TestNG报告和日志

    一.在进行自动化的过程中,日志一般采用log4j 2进行日志记录,但TestNG自己本身也带有日志记录功能(reporter),它的好处在于日志中记录的内容都是testng自动生成的. package ...