一、前言

Basic thread blocking primitives for creating locks and other synchronization classes.

用于创建锁定和其他同步类的基本线程阻塞原语(基础?)。

上面这段话是Java Doc对LockSupport的描述,表明了该类在实现锁当中的重要意义。因此我们先来查看一下其中的源码,看看它是如何实现的。

二、LockSupport成员变量分析

public class LockSupport {
private static final sun.misc.Unsafe UNSAFE;
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;
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); }
}
}
  1. 首先要明确的就是sun.misc.Unsafe这个类,它是一个final class,里面有100多个方法,锁的实现也是依赖了这个类,其中基本上都是native方法。Java避免了程序员直接操作内存,但这不是绝对的,通过使用Unsafe类,我们还是能够操作内存。笔者尝试阅读里面的C++代码,奈何已经将知识都还给了老师。源码越看到后面,越觉得C和C++的伟大,膝盖瑟瑟打抖,有兴趣的园友们可以尝试着阅读以下:Unsafe C++源码

  2. parkBlockerOffset。从字面上看就是parkBlocker的偏移量,那么parkBlocker是干嘛的呢,从static代码块中可以看到,它属于Thread类,于是进去看看:

/**
* The argument supplied to the current call to
* java.util.concurrent.locks.LockSupport.park.
* Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
* Accessed using java.util.concurrent.locks.LockSupport.getBlocker
*/
volatile Object parkBlocker;

从注释上看,就是给LockSupport的setBlocker和getBlocker调用。另外在LockSupport的java doc中也写到:

This object is recorded while the thread is blocked to permit monitoring and diagnostic tools to identify the reasons that threads are blocked. (Such tools may access blockers using method [getBlocker(Thread).) The use of these forms rather than the original forms without this parameter is strongly encouraged. The normal argument to supply as a blockerwithin a lock implementation is this.

大致是说,parkBlocker是当线程被阻塞的时候被记录,以便监视和诊断工具来识别线程被阻塞的原因。

Unsafe类提供了获取某个字段相对 Java对象的“起始地址”的偏移量的方法objectFieldOffset,从而能够获取该字段的值。

那么为什么记录该blocker在对象中的偏移量,而不是直接调用Thread.getBlocker(),这样不是更好,原因其实很好理解,当线程被阻塞(Blocked)的时候,线程是不会响应的。另外通过反射应该也可以拿到。

三、LockSupport的重要方法

类中的方法主要分为两类:park(阻塞线程)和unpark(解除阻塞)。

首先强调的一点事park方法阻塞的是当前的线程,也就是说在哪个线程中调用,那么哪个线程就被阻塞(在没有获得许可的情况下)。

重点讲其中的几个:

3.1 park()解析

public static void park() {
UNSAFE.park(false, 0L);
}

UNSAFE.park的两个参数,前一个为true的时候表示传入的是绝对时间,false表示相对时间,即从当前时间开始算。后面的long类型的参数就是等待的时间,0L表示永久等待。

根据java doc中的描述,调用park后有三种情况,能使线程继续执行下去:

  1. 有某个线程调用了当前线程的unpark。
  2. 其他线程中断(interrupt)了当前线程
  3. 该调用不合逻辑地(即毫无理由地)返回。

验证一:

public class UnparkTest {
public static void main(String[] args) throws InterruptedException {
Thread ut = new Thread(new UnparkThread(Thread.currentThread()));
ut.start();
System.out.println("I'm going to call park");
// Thread.sleep(1000L);
LockSupport.park();
System.out.println("oh, I'm running again"); }
} class UnparkThread implements Runnable {
private final Thread t;
UnparkThread(Thread t) {
this.t = t;
} @Override
public void run() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("I'm in unpark");
LockSupport.unpark(t);
System.out.println("I called unpark");
}
}

结果:

I'm going to call park
I'm in unpark
I called unpark
oh, I'm running again

另外值得一提的是,LockSupport对park和unpark的调用顺序并没有要求,将两个Thread.sleep(1000L);注释切换一下就可以发现,先调用unpark,再调用park,依旧可以获得许可,让线程继续运行。这一点与Object的 wait 和 notify 要求固定的顺序不同,其实现原理可以看这里

验证二:

public class LockSupportInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new InterruptThread());
t.start();
Thread.sleep(1000L);
System.out.println("I'm going to interrupt");
t.interrupt();
}
} class InterruptThread implements Runnable {
@Override
public void run() {
System.out.println("I'm going to park");
LockSupport.park();
System.out.println("I'm going to again");
}
}

运行结果:

I'm going to park
I'm going to interrupt
I'm going to again

LockSupport的park能够能响应interrupt事件,且不会抛出InterruptedException异常。

3.2 park(Object blocker)

park的另一个重载方法需要传入blocker对象:

public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}

在理解了parkBlocker的作用后,这个方法里的代码就很好理解了。

  1. 在调用park阻塞当前线程之前,先记录当前线程的blocker。
  2. 调用park阻塞当前线程
  3. 当前面提到的三个让线程继续执行下去的情况时,再将parkBlocker设置为null,因为当前线程已经没有被blocker住了,如果不设置为null,那诊断工具获取被阻塞的原因就是错误的,这也是为什么要有两个setBlocker的原因。

再看一下setBlocker的代码:

private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}

方法是私有的,嗯,为了保证正确性,肯定不能被其他类调用。

另外就是利用了之前提到的偏移量以及unsafe对象将blocker值设置进了线程t当中。

3.3 unpark(Thread thread)

public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}

这就很简单了,判断是否为空,然后调用unsafe的unpark方法。由此更可见unsafe这个类的重要性。

四、各种例子

4.1 jstack查看parkBlocker

前面提到parkBlocker提供了调试工具上面查找原因,所以我们来看一下在jstack上面是什么情况:

public class JstackTest {
public static void main(String[] args) {
// 给main线程设置名字,好查找一点
Thread.currentThread().setName("jstacktest");
LockSupport.park("block");
}
}

利用park(blocker)来阻塞main线程,传入string作为parkBlocker。

运行之后,在shell里运行:

> jps
37137 Jps
4860
37132 Launcher
37133 JstackTest

可以看到我们的java线程的pid,JstackTest这个类对应的是37133,然后再利用jstack来查看:

> jstack -l 37133
"jstacktest" #1 prio=5 os_prio=31 tid=0x00007f7f07001800 nid=0x2903 waiting on condition [0x0000700000901000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000079582f5d0> (a java.lang.String)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at lock.JstackTest.main(JstackTest.java:11) Locked ownable synchronizers:
- None

省略了一部分,可以看到jstacktest线程的状态是Waiting on condition(等待资源,或等待某个条件的发生),同事可以看到这样一句话:parking to wait for <0x000000079582f5d0> (a java.lang.String)。

<0x000000079582f5d0>的类型是String,也就是之前传入park里的block字符串。而0x000000079582f5d0估计就是其地址(待验证)。

4.2 利用LockSupport实现先进先出锁

在来看一下java doc上提供的示例:

class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false);
private final Queue<Thread> waiters
= new ConcurrentLinkedQueue<Thread>(); public void lock() {
boolean wasInterrupted = false;
Thread current = Thread.currentThread();
waiters.add(current);
while (waiters.peek() != current ||
!locked.compareAndSet(false, true)) {
LockSupport.park(this);
if (Thread.interrupted())
wasInterrupted = true;
} waiters.remove();
if (wasInterrupted)
current.interrupt();
} public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}

先进先出锁就是先申请锁的线程最先获得锁的资源,实现上采用了队列再加上LockSupport.park。

  1. 将当前调用lock的线程加入队列
  2. 如果等待队列的队首元素不是当前线程或者locked为true,则说明有线程已经持有了锁,那么调用park阻塞其余的线程。
  3. 如果队首元素是当前线程且locked为false,则说明前面已经没有人持有锁,删除队首元素也就是当前的线程,然后当前线程继续正常执行。
  4. 执行完后调用unlock方法将锁变量修改为false,并解除队首线程的阻塞状态。此时的队首元素继续之前的判断。

五、总结

到这里,对LockSupport有了简单的认识,如果还想深入了话,就要开始阅读C++里面的代码了。后面有机会再重拾C++。最后谢谢各位园友观看,如果有描述不对的地方欢迎指正,与大家共同进步!

参考:java-LockSupport详解

【JDK1.8】JUC——LockSupport的更多相关文章

  1. 【JDK1.8】JUC.Lock综述

    一.前言 前段时间结束了jdk1.8集合框架的源码阅读,在过年的这段时间里,一直在准备JUC(java.util.concurrent)的源码阅读.平时接触的并发场景开发并不很多,但是有网络的地方,就 ...

  2. 【JDK1.8】JUC——AbstractQueuedSynchronizer

    一.前言 在上一篇中,我们对LockSupport进行了阅读,因为它是实现我们今天要分析的AbstractQueuedSynchronizer(简称AQS)的基础,重新用一下最开始的图: 可以看到,在 ...

  3. 【JDK1.8】JUC——ReentrantLock

    一.前言 在之前的几篇中,我们回顾了锁框架中比较重要的几个类,他们为实现同步提供了基础支持,从现在开始到后面,就开始利用之前的几个类来进行各种锁的具体实现.今天来一起看下ReentrantLock,首 ...

  4. 【JDK1.8】Java 8源码阅读汇总

    一.前言 ​ 万丈高楼平地起,相信要想学好java,仅仅掌握基础的语法是远远不够的,从今天起,笔者将和园友们一起阅读jdk1.8的源码,并将阅读重点放在常见的诸如collection集合以及concu ...

  5. 【Java多线程】JUC包下的工具类CountDownLatch、CyclicBarrier和Semaphore

    前言 JUC中为了满足在并发编程中不同的需求,提供了几个工具类供我们使用,分别是CountDownLatch.CyclicBarrier和Semaphore,其原理都是使用了AQS来实现,下面分别进行 ...

  6. 【JDK1.8】JDK1.8集合源码阅读——总章

    一.前言 今天开始阅读jdk1.8的集合部分,平时在写项目的时候,用到的最多的部分可能就是Java的集合框架,通过阅读集合框架源码,了解其内部的数据结构实现,能够深入理解各个集合的性能特性,并且能够帮 ...

  7. 【JDK1.8】JDK1.8集合源码阅读——HashMap

    一.前言 笔者之前看过一篇关于jdk1.8的HashMap源码分析,作者对里面的解读很到位,将代码里关键的地方都说了一遍,值得推荐.笔者也会顺着他的顺序来阅读一遍,除了基础的方法外,添加了其他补充内容 ...

  8. 【JDK1.8】JDK1.8集合源码阅读——TreeMap(一)

    一.前言 在前面两篇随笔中,我们提到过,当HashMap的桶过大的时候,会自动将链表转化成红黑树结构,当时一笔带过,因为我们将留在本章中,针对TreeMap进行详细的了解. 二.TreeMap的继承关 ...

  9. 【Java并发】JUC—ReentrantReadWriteLock有坑,小心读锁!

    好长一段时间前,某些场景需要JUC的读写锁,但在某个时刻内读写线程都报超时预警(长时间无响应),看起来像是锁竞争过程中出现死锁(我猜).经过排查项目并没有能造成死锁的可疑之处,因为业务代码并不复杂(仅 ...

随机推荐

  1. Win调整和小技巧

    推荐win下一些个人爱用的工具软件(以及使用心得)和一些系统调整方法,让win下不尽人意的设置发生小小变化,让整天摸着电脑的ITer们的生活更有乐趣. 本人酷爱收集一些好用的软件,若各位也对某个或某些 ...

  2. 1.由浅入深解析 SimpleDateFormat

    一.SimpleDateFormat简介 SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类. 它允许格式化 (date -> text).语法分析 (text - ...

  3. python实现京东秒杀

    # _*_coding:utf-8_*_ from selenium import webdriver import datetime import time driver = webdriver.C ...

  4. Scrum 冲刺 第一日

    Scrum 冲刺 第一日 站立式会议 燃尽图 Alpha 阶段认领任务 明日任务安排 项目预期任务量 成员贡献值计算规则 今日贡献量 参考资料 站立式会议 返回目录 燃尽图 返回目录 Alpha 阶段 ...

  5. Flask 学习 十四 测试

    获取代码覆盖报告 安装代码覆盖工具 pip install coverage manage.py 覆盖检测 COV = None if os.environ.get('FLASK_COVERAGE') ...

  6. 搭建java环境——使用Sublime Text 3(windows环境)

    实现sublime Text 3对Java编译执行 参考网址:http://tieba.baidu.com/p/2609515186 1.1直接在安装路径下找到*\Packages\Java.subl ...

  7. slf4j 与 log4j2 实战讲解与日志分割

    这两天搭建项目的时候用到log4j2在这里把自己的问题与了解拿出来与大家分享一下. 1.为什我要用 因为,使用slf4j可以很好的保证我们的日志系统具有良好的兼容性,兼容当前常见几种日志系统,而使用l ...

  8. ESP8266 wifi 模块配置,Wechat+APP控制实现

    首先刷入安信可的AiCloud 2.0 SDK文件,AiCloud 2.0具体信息参见AiCloud 1.0 和AiCloud 2.0对比 APP见如下二维码下载. 1.安信可AiCloud 2.0 ...

  9. Python内置函数(57)——print

    英文文档: print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False) Print objects to the text str ...

  10. SpringBoot应用的启动方式

    一:IDE 运行Application这个类的main方法 二:在SpringBoot的应用的根目录下运行mvn spring-boot:run 三:使用mvn install 生成jar后运行 先到 ...