-
Java高并发20-并发包中锁原理解析(二)
一、例子
-
下面来一个例子加深对park和unpark的理解
package com.ruigege.LockSourceAnalysis6;
import java.util.concurrent.locks.LockSupport;
public class TestParkAndUnpark {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread begin park");
//调用park方法,挂起自己
LockSupport.park();
System.out.println("child thread end park");
System.out.println("今天又学了一个快捷键,sysout + alt +/ 是控制台" +
"输出的一个快捷键");
}
});
// 启动子线程
thread.start();
Thread.sleep(1000); // 主线程休眠一秒钟,目的是能够让子线程及时使用
System.out.println("main thread begin unpark");
LockSupport.unpark(thread); // 调用unpark方法,能够让子线程thread持有许可证,
// 然后park方法返回
}
}
-
下面来解释一下这个类的主要功效 -
首先建立了一个子线程,然后调用park方法,由于默认情况下,子线程没有持有许可证,因此它会把自己挂起;在主线程中执行了unpark方法,参数为子线程,这样做的目的就是让子线程能够持有许可证,然后子线程调用的park方法就会返回 -
注意点:park方法不会告诉我们是因为哪种原因返回的,因此调用者需要根据之前调用park方法的原因,再次检查条件是否满足,如果不满足的话,还需再次调用park方法 -
例如:根据调用前后的中断状态的对比可以判断是不是因为被中断才返回的。 -
下面为了说明调用park方法后的线程是因为被中断才返回的,我们修改代码
package com.ruigege.LockSourceAnalysis6;
import java.util.concurrent.locks.LockSupport;
public class TestParkAndUnpark {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread begin park");
//调用park方法,挂起自己
// LockSupport.park();
while(!Thread.currentThread().isInterrupted()) {
LockSupport.park();
}
System.out.println("child thread end park");
System.out.println("今天又学了一个快捷键,sysout + alt +/ 是控制台" +
"输出的一个快捷键");
}
});
// 启动子线程
thread.start();
Thread.sleep(1000); // 主线程休眠一秒钟,目的是能够让子线程及时使用
System.out.println("main thread begin unpark");
// LockSupport.unpark(thread); // 调用unpark方法,能够让子线程thread持有许可证,
// 然后park方法返回
thread.interrupt();
}
}
-
我们可以从中看出,如果只有中断了子线程,子线程才会运行结束,如果子线程不中断的话,即使调用了LockSupport(thread)方法,也不会中断。
二、void parkNanos(long nanos)方法
-
与park方法相类似,如果该线程没有拿到许可证,那么调用parkNanos(long nanos)方法该线程会立即停止阻塞,并返回;如果有许可证,那么nanos毫秒之后,该线程才会返回。 -
先举个例子
package com.ruigege.LockSourceAnalysis6;
import java.util.concurrent.locks.LockSupport;
public class TestPark {
public void testPark() {
LockSupport.park();
}
public static void main(String[] args) {
System.out.println("开始park方法");
TestPark testPark = new TestPark();
testPark.testPark();
}
}
-
下面是我们使用parkNanos方法来代替LockSupport.park()方法
LockSupport.park(this);
-
使用这个带参数的park(Object blocker)方法,当线程在没有持有许可证的时候,调用park方法,会被阻塞起来,这个blocker对象会被记录到该线程的内部。 -
使用jstack pid命令可以对线程堆栈进行查看,该线程内部是含有的什么对象
三、park(Object blocker)源码解析
public static void park2(Object blocker) {
// 获取当前线程
Thread t = Thread.currentThread();
// Thread对象中有一个volatile Object blocker
// 这里调用setter方法,把这个blocker记录到该线程的blocker对象中
setBlocker(t,blocker);
// 调用park方法对该线程进行阻塞
UNSAFE.park(false,0L); // UNSAFE其实是该线程的Unsafe变量,我们
// 这里省略前面的定义,直接拿来解释
setBlocker(t,null);
// 最后我们又把blocker对象置为空,这是因为已经停止阻塞了
// 这个blocker对象多用于线程阻塞的时候用来分析原因用的
}
-
基本都写在了方法的解释之后
四、void parkNanos(Object blocker,long nanos)方法
-
其实就是多了一个可以设置的超时时间
五、void parkUtil(Object blocker,long deadline)方法
-
这个方法和parkNanos不同的就是超时时间的算法,parkNanos的超时时间是从线程阻塞开始算起的,而parkUtil方法的超时时间是从1970年开始算起,到某一个时间点的毫秒数
public static void parkUtile(Object blocker,long deadline) {
Thread t = Thread.currentThread();
setBlocker(t,blocker);
UNSAFE.park(false,deadline);
setBlocker(t,null);
}
六、下面再看一个例子
package com.ruigege.LockSourceAnalysis6;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
public class FIFOMutex {
private final AtomicBoolean locked = new AtomicBoolean(false); // 一个boolean类的锁
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); // 一个高并发队列
public void lock() {
boolean wasInterrupted = false; // 中断的标志
Thread current = Thread.currentThread();
waiters.add(current); // 队列中添加这个线程
// (1)
while(waiters.peek() != current || !locked.compareAndSet(false,true)) {
// 复习compareAndSet方法,第一个参数是期盼的值,第二个就是如果就是期盼的值,那么就
// 设置为第二个参数,然后返回true
LockeSupport.park(this);
if(Thread.interrupted()) { // (2)
wasInterrupted = true;
}
}
waiters.remove();
if(wasInterrupted) { // (3)
current.interrupt();
}
}
public void unlock() {
locked.set(false);
LockSupport.unpark(waiters.peek());
}
}
-
这是一个先进先出的锁,也就是只有队列的首元素可以获取锁,在代码(1)如果当前线程不是队首或者当前锁已经被其他线程获取,那么调用park方法挂起自己。 -
然后再代码(2)处做判断,如果park方法是因为被中断而返回的,则忽略中断,并且重置中断标志,复习该方法去 -
在代码(3)中,判断标记,如果标记为true那么中断该线程 -
总结:其实就是其他线程中断了该线程,虽然我对中断信号不感兴趣,忽略它(也就是代码(2)),但是不代表其他线程对该标志不感兴趣,我们还需要恢复一下。
七、源码:
-
所在包:com.ruigege.ConcurrentListSouceCodeAnalysis5 -
https://github.com/ruigege66/ConcurrentJava
-
CSDN:https://blog.csdn.net/weixin_44630050 -
博客园:https://www.cnblogs.com/ruigege0000/
出 处:https://www.cnblogs.com/ruigege0000/p/14317817.html
最新更新
带有参数的装饰器
类装饰器
django中的auth模块与admin后台管理
python的日期处理
字符串常用方法
基本数据类型概述
python-map()函数基本用法
python带你实现任意下载AcFun视频数据~
bbs项目之注册功能
变量的定义和使用
三大常用数据库事务详解之三:事务运行
三大常用关系型数据库事务详解之二:基
三大关系型数据库事务详解之一:基本概
MongoDB常用命令(2)
MongoDB基本介绍与安装(1)
SQLServer触发器调用JavaWeb接口
SQL Server索引的原理深入解析
SqlServer2016模糊匹配的三种方式及效率问题
SQL中Truncate的用法
sqlserver 多表关联时在where语句中慎用tri
VB.NET中如何快速访问注册表
ASP.NET中图象处理过程详解
Vue(1)Vue安装与使用
JavaScript 语言入门
js将一段字符串的首字母转成大写
纯原生html编写的h5视频播放器
H5仿原生app短信验证码vue2.0组件附源码地
TypeScript(4)接口
TypeScript(3)基础类型
TypeScript(2)WebStorm自动编译TypeScript配置