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 ...
 
随机推荐
- 安装supervisord
			
一:简介 supervisord是一个进程管理工具,提供web页面管理,能对进程进行自动重启等操作. 优点: - 可以将非后台运行程序后台运行 - 自动监控,重启进程 缺点: - 不能管理后台运行程序 ...
 - 我的第一个python web开发框架(19)——产品发布相关事项
			
好不容易小白将系统开发完成,对于发布到服务器端并没有什么经验,于是在下班后又找到老菜. 小白:老大,不好意思又要麻烦你了,项目已经弄完,但要发布上线我还一头雾水,有空帮我讲解一下吗? 老菜:嗯,系统上 ...
 - C# 调用动态链接库,给游览器写入Cookie
			
样例代码: class Program { /// <summary> /// 写 /// </summary> /// <param name="lpszUr ...
 - redis 安装方式
			
1 参考网址 https://www.cnblogs.com/ahjx1628/p/6496529.html https://www.cnblogs.com/smail-bao/p/6164132.h ...
 - Dubbo(三) 安装Zookeeper 单机-集群
			
一.下载zookeeper zookeeper下载地址:https://www.apache.org/dyn/closer.cgi/zookeeper/点击下载 二.启动配置 选择合适版本下载后解压到 ...
 - 快速恢复开发环境(系统还原后的思考,附上eclipse注释的xml配置文件)
			
1.Eclipse/Myeclipse的工作空间,不能放在系统盘 除非你的项目都有实时的云同步或SVN等,才能放在系统固态盘,不然你享受快速启动项目的同时,也需要承担系统奔溃后找不回项目的风险: 公司 ...
 - Fiddler中设置断点修改返回结果Response
			
测试有时会遇到需要测试返回不同的数据前端展示出来会如何?如果去数据库中的数据会比较麻烦.这样我们可以通过fiddler设置断点来修改返回的数据实现测试不同的数据展示. 1.设置断点 (1)点击菜单栏按 ...
 - SourceTree管理工具的一些使用总结
			
一.冲突解决 在团队合作中,如果两个人同时修改一个文件 ,这个时候如果合并他人提交的代码是会产生冲突的,怎么解决? 1.先将代码提交至本地服务器 2.合并他人代码,这个时候在工作副本中会显示我们冲突的 ...
 - 【转1】Appium 1.6.3 在Xcode 8, iOS 10.2(模拟器)测试环境搭建 经验总结
			
Appium 1.6.3 在Xcode 8, iOS 10.2(模拟器)测试环境搭建 经验总结 关于 Appium 1.6.3 在Xcode 8, 10.2 的iOS模拟器上的问题很多,本人也差点放弃 ...
 - python 中文编码
			
import sys sys.setdefaultencoding('utf-8') 保存为:sitecustomize.py 将文件放至: /Library/Frameworks/Python.fr ...