一、前言

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. spring MVC 原理及源码解析

    首先要知道springmvc的核心控制器dispatcherServlet(继承自httpServlet) 一般httpServlet的请求过程: 1.初始化(创建servlet实例时)时会执行ser ...

  2. Java基础学习笔记五 Java基础语法之面向对象

    面向对象 理解什么是面向过程.面向对象 面向过程与面向对象都是我们编程中,编写程序的一种思维方式.面向过程的程序设计方式,是遇到一件事时,思考“我该怎么做”,然后一步步实现的过程.例如:公司打扫卫生( ...

  3. 连接数据后,当执行查询语句报错:ORA-01219: 数据库未打开: 仅允许在固定表/视图中查询

    参考博客:http://blog.csdn.net/lanchengxiaoxiao/article/details/40982771 1.在cmd窗口通过sqlplus连接数据库 C:\Users\ ...

  4. 软件工程网络15团队作业1——团队组队&展示

    Deadline: 2018-3-25 10:00PM,以提交至班级博客时间为准. 申请开通团队博客,并将团队博客地址发表在本次随笔的评论中 团队展示 根据5-6人的组队要求,每个队伍创建团队博客并发 ...

  5. python 操作PostgreSQL

    pip install psycopg Python psycopg2 模块APIs 以下是psycopg2的重要的的模块例程可以满足Python程序与PostgreSQL数据库的工作. S.N. A ...

  6. img加载卡顿,解决办法

    我觉得我在这个项目里遇到了太多的第一次.比如上一篇博文:在在360.UC等浏览器,img不加载原因. 当前情况是:图片加载缓慢,图片加载时出现卡顿. 上图:我缩放了图片,估计有点变形.能说明情况就行, ...

  7. js实现短暂提示框

    业务场景:当鼠标移入某元素时,显示提示框进行介绍.当鼠标移除时,会自动消失.引入ToolTip.js和ToolTip.css 主方法:ToolTip.show(需要提示的元素id, 随意不重复即可, ...

  8. 开发者的如何优雅的使用OSX

    Mac对于IT开发者来说是最好的开发工具,没有之一. 但是对于大部分人来说,第一个接触的PC操作系统都是Windows系统,此文将带大家优雅的快速学习和使用Mac的OSX系统. 1. 从键盘说起 Ma ...

  9. Java面试题合集(一)

    接下来几篇文章准备系统整理一下有关Java的面试题,分为基础篇,javaweb篇,框架篇,数据库篇,多线程篇,并发篇,算法篇等等,陆续更新中. 其他方面如前端后端等等的面试题也在整理中,都会有的. 所 ...

  10. 构建自己的 PHP 框架

    这是一个系列的文章,项目地址在这里,欢迎大家star. 这个框架前一部分比较像Yii,后一部分比较像Laravel,因为当时正在看相应框架的源码,所以会有不少借鉴参考.捂脸- 这个框架千万不要直接应用 ...