LockSupport理解
一、背景
在看并发包源码的时候看见过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理解的更多相关文章
- 关于LockSupport
concurrent包的基础 Doug Lea 的神作concurrent包是基于AQS (AbstractQueuedSynchronizer)框架,AQS框架借助于两个类:Unsafe(提供CAS ...
- 多线程锁--怎么理解Condition
在java.util.concurrent包中,有两个很特殊的工具类,Condition和ReentrantLock,使用过的人都知道,ReentrantLock(重入锁)是jdk的concurren ...
- Java阻塞中断和LockSupport
在介绍之前,先抛几个问题. Thread.interrupt()方法和InterruptedException异常的关系?是由interrupt触发产生了InterruptedException异常? ...
- 【转载】怎么理解Condition
注:本文主要介绍了Condition和ReentrantLock工具实现独占锁的部分代码原理,Condition能在线程之间协调通信主要是AbstractQueuedSynchronizer和cond ...
- 从Java视角理解CPU上下文切换(Context Switch)
从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态 在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快 ...
- 深入理解ReentrantLock
在Java中通常实现锁有两种方式,一种是synchronized关键字,另一种是Lock.二者其实并没有什么必然联系,但是各有各的特点,在使用中可以进行取舍的使用.首先我们先对比下两者. 实现: 首先 ...
- 怎么理解Condition(转)
在java.util.concurrent包中,有两个很特殊的工具类,Condition和ReentrantLock,使用过的人都知道,ReentrantLock(重入锁)是jdk的concurren ...
- AQS阻塞唤醒工具LockSupport
LockSupport在JDK源码中描述为:构建锁和其他同步类的基本线程阻塞原语,构建更高级别的同步工具集.LockSupport提供的park/unpark从线程的粒度上进行阻塞和唤醒,park/u ...
- 深入理解Java虚拟机--下
深入理解Java虚拟机--下 参考:https://www.zybuluo.com/jewes/note/57352 第10章 早期(编译期)优化 10.1 概述 Java语言的"编译期&q ...
随机推荐
- unity demo之坦克攻击
先展示一下成果吧,节后第一天上班简直困爆了,所以一定要动下脑子搞点事情. 分析: 1.涉及到的游戏对象有:坦克,摄像机,场景素材(包含灯光),子弹 2.坦克具有的功能:移动,旋转,发射子弹,记录生命值 ...
- thinkphp 3.1.3 redis 只能读取 无法写入的问题
找到thinkphp的目录 thinkphp\Extend\Driver\Cache 下面的Redis 大概在81行足有 // if(is_int($expire)) { // redis ...
- permission denied for window type 2003
今天在做系统悬浮窗的时候出现权限拒绝,类型是2003,这里要说下,做系统悬浮窗需要申请权限,6.0以上的 还需要动态申请下,这里我就不过多描述了, 我在申请完权限后仍然不行,这里主要是出现在了这个类型 ...
- Vue 爬坑之路(八)—— 使用 Echarts 创建图表
在后台管理系统中,图表是一个很普遍的元素.目前常用的图标插件有 charts, Echarts, highcharts.这次将介绍 Echarts 在 Vue 项目中的应用. 一.安装插件 使用 c ...
- ubuntu16.04编译安装php7.2
1,下载解压 tar xf php-7.2.0.tar.gz cd php-7.2.0/ 2,安装必要的库 sudo apt-get install libxml2-devsudo apt-get i ...
- 记录一次APP的转让流程
由于业务需要,需要将开发的App从一个账号(A账号)转移到另一个账号(B账号),这里简单介绍一下转让流程.主要包括两大步骤: 转让方(A账号)提出转让申请 接收方(B账号)接受转让App 如果不想看这 ...
- NoSQL:Linux操作memcached
一 NoSQL简介 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",泛指非关系型的数据库,随着互联网web2.0网站的兴起,传统的关系数据库在应付 ...
- GO开发[二]:golang语言基础
[TOC] 一.变量 1.声明变量 变量相当于是对一块数据存储空间的命名,程序可以通过定义一个变量来申请一块数据存储空间,之后可以通过引用变量名来使用这块存储空间. Go语言引入了关键字var,而类型 ...
- [转载]mysql绑定参数bind_param原理以及防SQL注入
假设我们的用户表中存在一行.用户名字段为username.值为aaa.密码字段为pwd.值为pwd.. 下面我们来模拟一个用户登录的过程.. <?php $username = "aa ...
- 移动端吸顶(iOS与安卓)
有的时候经常会遇到移动端吸顶效果,开始我也只是上网查了一下,分别有iOS和android两种样式,如下: /*!*Android*!*/ .head { position: fixed; top: 0 ...