一、背景

在看并发包源码的时候看见过LockSupport,今天恰巧看到LockSupport字眼,于是看下jdk1.7中的源码结构。想着它应该是运用多线程的锁工具的,到底似乎怎么实现的呢?

二、使用

于是自己写个demo对比下synchronized

场景:main线程中创建一个线程A,想让threadA 循环完毕的时候先阻塞,然后再main线程中释放。

1.控制多线程并发:

 public static void main(String[] args) {
Object obj = new Object();
generalSync(obj);
obj.notifyAll();
System.out.println("主线程执行完毕");
} public static void generalSync(final Object obj) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
             System.out.println(i);
}
System.out.println("循环完毕");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A执行完毕");
}
};
Thread threadA = new Thread(runnable);
threadA.start();
}

上面这个最终结果是什么呢情?会以异常结束:java.lang.IllegalMonitorStateException, 产生的原因是Object的wait,notify,notifyAll方法必须在同步块中执行

2.使用synchronized但主线程接触阻塞在threadA阻塞执行之前

 public static void main(String[] args) {
Object obj = new Object();
generalSync(obj);
       // Thread.sleep(1000);
synchronized (obj) {
obj.notifyAll();
}
System.out.println("主线程执行完毕");
} public static void generalSync(final Object obj) {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
System.out.println("循环完毕");
try {
synchronized (obj) {
obj.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A执行完毕");
}
};
Thread threadA = new Thread(runnable);
threadA.start();
}

上面会导致一直阻塞,因为主线程在第3行开启一个threadA后,就往下执行4~7代码,而threadA需要循环所用的时间更久。因此会先调用obj.notifyAll(),然后再obj.wait()导致调用顺序错误一直阻塞。想要达到我们预期的目的,根据threadA时间,可以在第4行添加Thread.sleep(1000)使主线程中的obj.notifyAll()晚于obj.wait()执行。

3.使用LockSupport来实现同步

     public static void main(String[] args) {
Thread threadA = generalSync();
LockSupport.unpark(threadA);
System.out.println("主线程执行完毕");
} public static Thread generalSync() {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
System.out.println("循环完毕");
LockSupport.park();
System.out.println("线程A执行完毕");
}
};
Thread threadA = new Thread(runnable);
threadA.start();
return threadA;
}

通过LockSupport无需关于阻塞和释放的调用先后问题,仅仅通过park/unpark即可阻塞或释放。park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态

三、阅读源码

通过类注释介绍,LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通过调用 LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒 的,下面重点关注着2个方法。

 public class LockSupport {
private LockSupport() {} // Cannot be instantiated. // Hotspot implementation via intrinsics API
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long parkBlockerOffset;

1.根据上面的类定义,内部构造方法私有化,外部类无法实例,作为工具类使用的意图。类中实例化了一个Unsafe这个可直接操作内存的本地API

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

2.阻塞是通过park方法,第2行获取当前线程;第3行执行setBlocker(Thread t, Object arg),setBlocker从名字感觉设置阻塞的地方;第4行调用Unsafe中public native void park(boolean paramBoolean, long paramLong)产生阻塞;第5行调用setBlocker(Thread t, Object arg),但是Object参数是null,此处应该是去除阻塞点。

 public class LockSupport {
private LockSupport() {} // Cannot be instantiated. // Hotspot implementation via intrinsics API
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long parkBlockerOffset; static {
try {
parkBlockerOffset = unsafe.objectFieldOffset
(java.lang.Thread.class.getDeclaredField("parkBlocker"));
} catch (Exception ex) { throw new Error(ex); }
} private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
unsafe.putObject(t, parkBlockerOffset, arg);
}

3.setBlocker是调用Unsafe中public native void putObject(Object paramObject1, long paramLong, Object paramObject2)方法,这个long类型paramLong传的是Thread在内存中的偏移量。通过8~13可以看出在静态代码块中通过Unsafe中public native long objectFieldOffset(Field paramField)来得到内存中位置(之前原子操作类也是通过此方法来获取偏移量)这个偏移量属性名称是"parkBlocker",类型从第11行可以知道parkBlocker是java.lang.Thread类中的一个属性,

public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
} ... ...
/**
* 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;

这个parkBlocker应该是Thread类变量。首先LockSupport 是构造方法私有化,类似一个工具类,而且parkBlockerOffset是static类型的,所以即使在多个场景下的多线程环境,不同的多个Thread调用setBlocker方法都只是针对Thread的类变量进行赋值(类变量只有一个)所以是多对一的关系。并且通过getBlocker源码注释可以看出

     /**
* Returns the blocker object supplied to the most recent
* invocation of a park method that has not yet unblocked, or null
* if not blocked. The value returned is just a momentary
* snapshot -- the thread may have since unblocked or blocked on a
* different blocker object.
*
* @param t the thread
* @return the blocker
* @throws NullPointerException if argument is null
* @since 1.6
*/
public static Object getBlocker(Thread t) {
if (t == null)
throw new NullPointerException();
return unsafe.getObjectVolatile(t, parkBlockerOffset);
}

从注释看出“返回的是最近被阻塞的对象,类似一个瞬间的快照”,那么我理解Thread中Object parkBlocker属性可能会被多个线程赋值。这个属性跟并发阻塞并无关系,只是起到记录阻塞对象的作用。

至于Unsafe类中native方法,就没有去追究。看其他博客理解到的是:在hotspot里每个java线程都有一个Parker实例,Parker里使用了一个无锁的队列在分配释放Parker实例。Parker里面有个变量,volatile int _counter 相当于许可,二元信号量(类似0和1)

当调用park时,先尝试直接能否直接拿到“许可”(即_counter>0时)如果成功,则把_counter设置为0,并返回;如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0

当unpark时,则简单多了,直接设置_counter为1,如果_counter之前的值是0,则还要调用唤醒在park中等待的线程:

LockSupport理解的更多相关文章

  1. 关于LockSupport

    concurrent包的基础 Doug Lea 的神作concurrent包是基于AQS (AbstractQueuedSynchronizer)框架,AQS框架借助于两个类:Unsafe(提供CAS ...

  2. 多线程锁--怎么理解Condition

    在java.util.concurrent包中,有两个很特殊的工具类,Condition和ReentrantLock,使用过的人都知道,ReentrantLock(重入锁)是jdk的concurren ...

  3. Java阻塞中断和LockSupport

    在介绍之前,先抛几个问题. Thread.interrupt()方法和InterruptedException异常的关系?是由interrupt触发产生了InterruptedException异常? ...

  4. 【转载】怎么理解Condition

    注:本文主要介绍了Condition和ReentrantLock工具实现独占锁的部分代码原理,Condition能在线程之间协调通信主要是AbstractQueuedSynchronizer和cond ...

  5. 从Java视角理解CPU上下文切换(Context Switch)

    从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态   在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快 ...

  6. 深入理解ReentrantLock

    在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock.二者其实并没有什么必然联系,但是各有各的特点,在使用中可以进行取舍的使用.首先我们先对比下两者. 实现: 首先 ...

  7. 怎么理解Condition(转)

    在java.util.concurrent包中,有两个很特殊的工具类,Condition和ReentrantLock,使用过的人都知道,ReentrantLock(重入锁)是jdk的concurren ...

  8. AQS阻塞唤醒工具LockSupport

    LockSupport在JDK源码中描述为:构建锁和其他同步类的基本线程阻塞原语,构建更高级别的同步工具集.LockSupport提供的park/unpark从线程的粒度上进行阻塞和唤醒,park/u ...

  9. 深入理解Java虚拟机--下

    深入理解Java虚拟机--下 参考:https://www.zybuluo.com/jewes/note/57352 第10章 早期(编译期)优化 10.1 概述 Java语言的"编译期&q ...

随机推荐

  1. Python连接webstocker获取消息

    简介(脚本都是根据网上资料改写) 此脚本主要是客户觉得webstcket不稳定,所以编辑一个脚本,不停的请求web服务器,当发生错误时,脚本自动退出(). 脚本内容 脚本一 # -*- coding: ...

  2. java 异常处理与返回

    try{ // 1. return ++x; }catch(){ }finally{ //2. x++; } 实际返回值还是 ++x后的结果,因为 ++x 后 x 的值会入栈,作为返回结果: 以上代码 ...

  3. 【原创】java NIO selector 学习笔记 一

    能力有限,仅仅是自己看源码的一些笔记. 主要介绍 可选通道 和 选择器 选择键(SelectableChannel  和 Selector SelectionKey) 选择器(Selector) 选择 ...

  4. spring的注解使用

    1.注解测试在xml里面配置<context:component-scan base-package="cn.ql"></component-scan>co ...

  5. 深入.NET数据类型(2)

    一.装箱和拆箱 将值类型转换为引用类型的过程称为装箱,反之称为拆箱 实际开发尽量避免 装/拆 箱 原因: 装/拆 箱都会降低程序性能 示例代码: static void Main(string[] a ...

  6. [js高手之路]面向对象版本匀速运动框架

    这篇文章的效果,需要看过以下3篇文章: [js插件开发教程]一步步开发一个可以定制配置的隔行变色小插件 [js高手之路]匀速运动与实例实战(侧边栏,淡入淡出) [js高手之路]打造通用的匀速运动框架 ...

  7. SEO的基本概念 和 提交SITEMAP到搜索引擎

    SEO的基本概念 SEO的基本概念 :我们接触的SEO主要就是做搜索引擎的优化,让搜索引擎更好地收录 我们的网站,不管是首页还是内页,都能有很好的收录和排名.那么我们经常用到的SEO的语句是什么呢?小 ...

  8. Effective Java 第三版——17. 最小化可变性

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. MySQL 行锁 表锁机制

    MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整的很惨!不知坑在何方?没事,我来给你们标记几个坑.遇到了可别乱踩.通过本章内容,带你学习MySQL的行锁 ...

  10. 【转载】Linux cgroup资源隔离各个击破之 - cpu隔离1

    Linux cgroup 有两个子系统支持CPU隔离.一个是cpu子系统,另一个是cpuset子系统. cpu子系统根据进程设置的调度属性,选择对应的CPU资源调度方法 .1. 完全公平调度 Comp ...