更多内容,前往IT-BLOG

LockSupport 用来创建锁和其他同步类的基本线程阻塞原语。简而言之,当调用 LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用 LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。

LockSupport 源码分析

 1 public class LockSupport {
2 // Hotspot implementation via intrinsics API
3 private static final sun.misc.Unsafe UNSAFE;
4 // 表示内存偏移地址
5 private static final long parkBlockerOffset;
6 // 表示内存偏移地址
7 private static final long SEED;
8 // 表示内存偏移地址
9 private static final long PROBE;
10 // 表示内存偏移地址
11 private static final long SECONDARY;
12
13 static {
14 try {
15 // 获取Unsafe实例
16 UNSAFE = sun.misc.Unsafe.getUnsafe();
17 // 线程类类型
18 Class<?> tk = Thread.class;
19 // 获取Thread的parkBlocker字段的内存偏移地址
20 parkBlockerOffset = UNSAFE.objectFieldOffset
21 (tk.getDeclaredField("parkBlocker"));
22 // 获取Thread的threadLocalRandomSeed字段的内存偏移地址
23 SEED = UNSAFE.objectFieldOffset
24 (tk.getDeclaredField("threadLocalRandomSeed"));
25 // 获取Thread的threadLocalRandomProbe字段的内存偏移地址
26 PROBE = UNSAFE.objectFieldOffset
27 (tk.getDeclaredField("threadLocalRandomProbe"));
28 // 获取Thread的threadLocalRandomSecondarySeed字段的内存偏移地址
29 SECONDARY = UNSAFE.objectFieldOffset
30 (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
31 } catch (Exception ex) { throw new Error(ex); }
32 }
33 }

UNSAFE字段表示 sun.misc.Unsafe类,查看其源码,点击在这里,一般程序中不允许直接调用,而 long型的表示实例对象相应字段在内存中的偏移地址,可以通过该偏移地址获取或者设置该字段的值。

类的构造函数:LockSupport 只有一个私有构造函数,无法被实例化。

1 // 私有构造函数,无法被实例化
2 private LockSupport() {}

在分析 LockSupport函数之前,先引入 sun.misc.Unsafe类中的 park和 unpark函数,因为 LockSupport的核心函数都是基于Unsafe类中定义的 parkunpark函数,下面给出两个函数的定义:

1 public native void park(boolean isAbsolute, long time);
2 public native void unpark(Thread thread);

对两个函数的说明如下:

【1】park函数:阻塞线程,并且该线程在下列情况发生之前都会被阻塞:① 调用 unpark函数,释放该线程的许可之前。② 该线程被中断之前。③ 设置的时间到之前。并且,当 time为绝对时间时,isAbsolute为 true,否则,isAbsolute为 false。当time为0时,表示无限等待,直到 unpark发生。
【2】unpark函数:释放线程的许可,激活调用 park后阻塞的线程。该函数不是安全的,调用该函数时要确保线程依旧存活。

park 函数

park 函数有两个重载版本,方法摘要如下:

1 public static void park();
2 public static void park(Object blocker);

两个函数的区别在于 park()函数有没有 blocker,即没有设置线程的 parkBlocker字段。park(Object)型函数如下。

 1 public static void park(Object blocker) {
2 // 获取当前线程
3 Thread t = Thread.currentThread();
4 // 设置Blocker
5 setBlocker(t, blocker);
6 // 获取许可
7 UNSAFE.park(false, 0L);
8 // 重新可运行后再此设置Blocker
9 setBlocker(t, null);
10 }

调用 park函数时,首先获取当前线程,然后设置当前线程的 parkBlocker字段,即调用 setBlocker函数,之后调用 Unsafe类的park函数,之后再调用 setBlocker函数。那么问题来了,为什么要在此 park函数中调用两次 setBlocker函数呢? 原因其实很简单,调用 park函数时,当前线程首先设置好 parkBlocker字段,然后再调用 Unsafe的 park函数,此后,当前线程就已经阻塞了,等待该线程的 unpark函数被调用,所以后面的一个 setBlocker函数无法运行,unpark函数被调用,该线程获得许可后,就可以继续运行了,也就运行第二个 setBlocker,把该线程的 parkBlocker字段设置为null,这样就完成了整个 park函数的逻辑。如果没有第二个 setBlocker,那么之后没有调用 park(Object blocker),而直接调用 getBlocker函数,得到的还是前一个 park(Object blocker)设置的 blocker,显然是不符合逻辑的。总之,必须要保证在 park(Object blocker)整个函数执行完后,该线程的parkBlocker字段又恢复为 null。所以,park(Object)型函数里必须要调用 setBlocker函数两次。setBlocker方法如下:此方法用于设置线程tparkBlocker字段的值为 arg

1 private static void setBlocker(Thread t, Object arg) {
2 // 设置线程t的parkBlocker字段的值为arg
3 UNSAFE.putObject(t, parkBlockerOffset, arg);
4 }

另外一个无参重载版本,park()函数如下。

1 public static void park() {
2 // 获取许可,设置时间为无限长,直到可以获取许可
3 UNSAFE.park(false, 0L);
4 }

调用了park函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。
【1】其他某个线程将当前线程作为目标调用 unpark;
【2】其他某个线程中断当前线程;
【3】该调用不合逻辑地(即毫无理由地)返回;

parkNanos 函数

此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。具体函数如下。该函数也是调用了两次 setBlocker函数,nanos参数表示相对时间,表示等待多长时间。

 1 public static void parkNanos(Object blocker, long nanos) {
2 if (nanos > 0) { // 时间大于0
3 // 获取当前线程
4 Thread t = Thread.currentThread();
5 // 设置Blocker
6 setBlocker(t, blocker);
7 // 获取许可,并设置了时间
8 UNSAFE.park(false, nanos);
9 // 设置许可
10 setBlocker(t, null);
11 }
12 }

parkUntil 函数

此函数表示在指定的时限前禁用当前线程,除非许可可用,具体函数如下:该函数也调用了两次 setBlocker函数,deadline参数表示绝对时间,表示指定的时间。

1 public static void parkUntil(Object blocker, long deadline) {
2 // 获取当前线程
3 Thread t = Thread.currentThread();
4 // 设置Blocker
5 setBlocker(t, blocker);
6 UNSAFE.park(true, deadline);
7 // 设置Blocker为null
8 setBlocker(t, null);
9 }

unpark 函数

此函数表示如果给定线程的许可尚不可用,则使其可用。如果线程在 park 上受阻塞,则它将解除其阻塞状态。否则,保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。具体函数如下:释放许可,指定线程可以继续运行。

1 public static void unpark(Thread thread) {
2 if (thread != null) // 线程为不空
3 UNSAFE.unpark(thread); // 释放该线程许可
4 }

使用 wait/notify实现线程同步

1 before wait
2 before notify
3 after notify
4 after wait

具体的流程图如下:

使用 wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,将起不了作用。具体代码如下:

 1 class MyThread extends Thread {
2 public void run() {
3 synchronized (this) {
4 System.out.println("before notify");
5 notify();
6 System.out.println("after notify");
7 }
8 }
9 }
10
11 public class WaitAndNotifyDemo {
12 public static void main(String[] args) throws InterruptedException {
13 MyThread myThread = new MyThread();
14 myThread.start();
15 // 主线程睡眠3s
16 Thread.sleep(3000);
17 synchronized (myThread) {
18 try {
19 System.out.println("before wait");
20 // 阻塞主线程
21 myThread.wait();
22 System.out.println("after wait");
23 } catch (InterruptedException e) {
24 e.printStackTrace();
25 }
26 }
27 }
28 }

运行结果:由于先调用了notify,再调用的wait,此时主线程还是会一直阻塞。

1 before notify
2 after notify
3 before wait

使用 park/unpark实现线程同步

 1 import java.util.concurrent.locks.LockSupport;
2
3 class MyThread extends Thread {
4 private Object object;
5
6 public MyThread(Object object) {
7 this.object = object;
8 }
9
10 public void run() {
11 System.out.println("before unpark");
12 try {
13 Thread.sleep(1000);
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17 // 获取blocker
18 System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
19 // 释放许可
20 LockSupport.unpark((Thread) object);
21 // 休眠500ms,保证先执行park中的setBlocker(t, null);
22 try {
23 Thread.sleep(500);
24 } catch (InterruptedException e) {
25 e.printStackTrace();
26 }
27 // 再次获取blocker
28 System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
29
30 System.out.println("after unpark");
31 }
32 }
33
34 public class test {
35 public static void main(String[] args) {
36 MyThread myThread = new MyThread(Thread.currentThread());
37 myThread.start();
38 System.out.println("before park");
39 // 获取许可
40 LockSupport.park("ParkAndUnparkDemo");
41 System.out.println("after park");
42 }
43 }

运行结果:

1 before park
2 before unpark
3 Blocker info ParkAndUnparkDemo
4 after park
5 Blocker info null
6 after unpark

本程序先执行 park,然后再执行 unpark,进行同步,并且在 unpark的前后都调用了getBlocker,可以看到两次的结果不一样,并且第二次调用的结果为null,这是因为在调用 unpark之后,执行了 Lock.park(Object blocker)函数中的 setBlocker(t, null)函数,所以第二次调用 getBlocker时为 null。

上例是先调用park,然后调用unpark,现在修改程序,先调用unpark,然后调用park,看能不能正确同步。具体代码如下:

 1 import java.util.concurrent.locks.LockSupport;
2
3 class MyThread extends Thread {
4 private Object object;
5
6 public MyThread(Object object) {
7 this.object = object;
8 }
9
10 public void run() {
11 System.out.println("before unpark");
12 // 释放许可
13 LockSupport.unpark((Thread) object);
14 System.out.println("after unpark");
15 }
16 }
17
18 public class ParkAndUnparkDemo {
19 public static void main(String[] args) {
20 MyThread myThread = new MyThread(Thread.currentThread());
21 myThread.start();
22 try {
23 // 主线程睡眠3s
24 Thread.sleep(3000);
25 } catch (InterruptedException e) {
26 e.printStackTrace();
27 }
28 System.out.println("before park");
29 // 获取许可
30 LockSupport.park("ParkAndUnparkDemo");
31 System.out.println("after park");
32 }
33 }

运行结果:

1 before unpark
2 after unpark
3 before park
4 after park

可以看到,在先调用 unpark,再调用 park时,仍能够正确实现同步,不会造成由 wait/notify调用顺序不当所引起的阻塞。因此park/unpark相比 wait/notify更加的灵活。

中断响应

 1 import java.util.concurrent.locks.LockSupport;
2
3 class MyThread extends Thread {
4 private Object object;
5
6 public MyThread(Object object) {
7 this.object = object;
8 }
9
10 public void run() {
11 System.out.println("before interrupt");
12 try {
13 // 休眠3s
14 Thread.sleep(3000);
15 } catch (InterruptedException e) {
16 e.printStackTrace();
17 }
18 Thread thread = (Thread) object;
19 // 中断线程
20 thread.interrupt();
21 System.out.println("after interrupt");
22 }
23 }
24
25 public class InterruptDemo {
26 public static void main(String[] args) {
27 MyThread myThread = new MyThread(Thread.currentThread());
28 myThread.start();
29 System.out.println("before park");
30 // 获取许可
31 LockSupport.park("ParkAndUnparkDemo");
32 System.out.println("after park");
33 }
34 }

运行结果:可以看到,在主线程调用 park阻塞后,在 myThread线程中发出了中断信号,此时主线程会继续运行,也就是说明此时 interrupt起到的作用与 unpark一样。

1 before park
2 before interrupt
3 after interrupt
4 after park

Thread.sleep() 和 Object.wait()的区别?

首先,我们先来看看 Thread.sleep()Object.wait()的区别,这是一个烂大街的题目了,大家应该都能说上来两点。
【1】Thread.sleep()不会释放占有的锁,Object.wait()会释放占有的锁;
【2】Thread.sleep()必须传入时间,Object.wait()可传可不传,不传表示一直阻塞下去;
【3】Thread.sleep()到时间了会自动唤醒,然后继续执行;
【4】Object.wait()不带时间的,需要另一个线程使用 Object.notify()唤醒;
【5】Object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;

其实,他们俩最大的区别就是Thread.sleep()不会释放锁资源,Object.wait()会释放锁资源。

Thread.sleep() 和 Condition.await()的区别?

Object.wait() Condition.await()的原理是基本一致的,不同的是 Condition.await()底层是调用 LockSupport.park()来实现阻塞当前线程的。实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让 state状态变量变为0,然后才是调用 LockSupport.park()阻塞当前线程。

Thread.sleep() 和 LockSupport.park()的区别?

LockSupport.park() 还有几个兄弟方法:parkNanos()parkUtil()等,我们这里说的park()方法统称这一类方法。
【1】从功能上来说,Thread.sleep()LockSupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源;
【2】Thread.sleep()没法从外部唤醒,只能自己醒过来;
【3】LockSupport.park()方法可以被另一个线程调用 LockSupport.unpark()方法唤醒;
【4】Thread.sleep()方法声明上抛出了 InterruptedException中断异常,所以调用者需要捕获这个异常或者再抛出;
【5】LockSupport.park()方法不需要捕获中断异常;
【6】Thread.sleep()本身就是一个 native方法;
【7】LockSupport.park()底层是调用的 Unsafenative方法;

Object.wait() 和 LockSupport.park()的区别?

二者都会阻塞当前线程的运行,他们有什么区别呢? 经过上面的分析相信你一定很清楚了,真的吗? 往下看!
【1】Object.wait()方法需要在 synchronized块中执行;
【2】LockSupport.park() 可以在任意地方执行;
【3】Object.wait() 方法声明抛出了中断异常,调用者需要捕获或者再抛出;
【4】LockSupport.park() 不需要捕获中断异常;
【5】Object.wait()不带超时的,需要另一个线程执行 notify()来唤醒,但不一定继续执行后续内容;
【6】LockSupport.park()不带超时的,需要另一个线程执行 unpark()来唤醒,一定会继续执行后续内容;
【7】如果在 wait()之前执行了 notify()会怎样? 抛出 IllegalMonitorStateException异常;
【8】如果在 park()之前执行了 unpark()会怎样? 线程不会被阻塞,直接跳过 park(),继续执行后续内容;
【9】park()/unpark()底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的 Semaphore,只不过这个信号量在重复执行 unpark()的时候也不会再增加许可证,最多只有一个许可证。

LockSupport.park()会释放锁资源吗?

不会,它只负责阻塞当前线程,释放锁资源实际上是在 Conditionawait()方法中实现的。

LockSupport 详解的更多相关文章

  1. LockSupport详解

    concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的,AQS框架借助于两个类: Unsafe(提供CAS操作) LockSupport(提供park/un ...

  2. Java 并发编程(一) → LockSupport 详解

    开心一刻 今天突然收到花呗推送的消息,说下个月 9 号需要还款多少钱 我就纳了闷了,我很长时间没用花呗了,怎么会欠花呗钱? 后面我一想,儿子这几天玩了我手机,是不是他偷摸用了我的花呗 于是我找到儿子问 ...

  3. 最强Java并发编程详解:知识点梳理,BAT面试题等

    本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...

  4. Lock的实现之ReentrantLock详解

    摘要 Lock在硬件层面依赖CPU指令,完全由Java代码完成,底层利用LockSupport类和Unsafe类进行操作: 虽然锁有很多实现,但是都依赖AbstractQueuedSynchroniz ...

  5. AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  6. 【Java并发】详解 AbstractQueuedSynchronizer

    前言 队列同步器 AbstractQueuedSynchronizer(以下简称 AQS),是用来构建锁或者其他同步组件的基础框架.它使用一个 int 成员变量来表示同步状态,通过 CAS 操作对同步 ...

  7. Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  8. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  9. (转)Java并发包基石-AQS详解

    背景:之前在研究多线程的时候,模模糊糊知道AQS这个东西,但是对于其内部是如何实现,以及具体应用不是很理解,还自认为多线程已经学习的很到位了,贻笑大方. Java并发包基石-AQS详解Java并发包( ...

  10. JDK中Unsafe类详解

    Java中Unsafe类详解 在openjdk8下看Unsafe源码 浅析Java中的原子操作 Java并发编程之LockSupport http://hg.openjdk.java.net/jdk7 ...

随机推荐

  1. 第三章:用python实现常用的用户分层模型(RFM模型)

    文章目录 项目背景 读取数据 数据分析 分析 Recent 分析 Frequency 分析 Mount RFM模型 分位数分层 自定义分层 定义客户标签 数据可视化 结论 源码地址 本文可以学习到以下 ...

  2. 1.3 C语言--指针与结构体

    指针 指针概念的引入 关于内存 程序有数据和指令组成,数据和指令在执行过程中存放在内存中.变量是程序数据中的一种,因此变量也存储在内存中:内存中的每个字节都有一个唯一的编码,即内存地址.32位机的内存 ...

  3. python 安装步骤

    1.这个安装方法不需要配置环境变量 2. 3. 4.进入cmd,输入python -v

  4. Maven常用参数及其说明

    Maven常用参数及其说明 -h,--help Display help information-am,--also-make 构建指定模块,同时构建指定模块依赖的其他模块;-amd,--also-m ...

  5. 关于集合set、数据类型转换等

  6. uniapp+vue3+ts

    1. 创建vue3的默认uniapp模板 2. npm init 创建package.json

  7. js根据输入天数,通过时间戳转日期时间,日期时间转时间戳,换算成多少天

    1.时间戳转日期时间 function timestampToDate(timestamp,index) {     var date = new Date(timestamp + index * 8 ...

  8. layui 点击显示与点击隐藏

    主要有lay-filter属性,靠这个属性监听 <div class="layui-col-xs12 layui-col-sm4 layui-col-md4"> < ...

  9. 使用Microsoft Network Monitor 抓包分析文件上传

    Microsoft 自己提供了一个官方的抓包工具,可以比较方便的在windows平台抓包,并可以提供协议关键字正则.安装包位置:\\192.168.10.248\public\ghw\tools\MN ...

  10. Hive 操作与应用 词频统计

    一.hive用本地文件进行词频统计 1.准备本地txt文件 2.启动hadoop,启动hive 3.创建数据库,创建文本表 4.映射本地文件的数据到文本表中 5.hql语句进行词频统计交将结果保存到结 ...