锁原理分析系列——LockSupport工具类

简介: 锁原理分析系列——LockSupport工具类

前面学习了很多Java并发编程的知识,很多知识点都离不开锁的支持,从这期开始,主要讲解一些Java并发包中的锁的原理,让我们来一起揭开它的神秘面纱吧。


LockSupport工具类


LockSupport是JDK中rt.jar包里的工具类,其主要作用是挂起和唤醒线程,它也是创建锁和其他同步类的基础。


LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的

LockSupport类是使用Unsafe类实现的


下面介绍LockSupport类的几个主要方法。


1、void park()方法


  • 若调用park方法的线程已经拿到了LockSupport关联的许可证,则调用LockSupport.park()时会马上返回
  • 否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。


如下代码:

public class LockSupportTest {
    public static void main(String[] args) {
        System.out.println("开始park");
        LockSupport.park();
        System.out.println("结束park");
    }
}


结果只会输出:开始park ,因为当前线程被挂起了,也就证明了上面所说的:默认情况下调用线程是不持有LockSupport的许可证的。


在其他线程调用void unpark(Thread thread)方法并将当前的线程作为参数时,调用park方法被阻塞的线程会返回。


如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,调用park方法被阻塞的线程也会返回。


注意:调用park方法而被阻塞的线程被其他线程中断而返回时,并不会抛出InterruptedException异常。


2、void unpark(Thread thread)方法


当一个线程调用void unpark(Thread thread)方法时:


如果作为参数的thread线程没有持有thread与LockSupport类关联的许可证,则让thread持有。

如果thread之前因为调用park()而被挂起,则调用了upark后,thread会被唤醒。

如果thread之前没有调用park(),则调用upark后,再次调用park()方法,会立即返回。


修改上述代码如下:

public class LockSupportTest {
    public static void main(String[] args) {
        System.out.println("开始park");
        //使当前线程获取到许可证
        LockSupport.unpark(Thread.currentThread());
        //调用park方法
        LockSupport.park();
        System.out.println("结束park");
    }
}

输出结果为:


开始park
结束park


下面再看一个例子加深park和unpark的理解:

public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("子线程开始park");
            LockSupport.park();//调用park,挂起自己
            System.out.println("子线程结束park");
        });
        //启动子线程
        thread.start();
        //主线程休眠1s
        Thread.sleep(1000);
        System.out.println("主线程开始unPark");
        //调用unpark方法,让thread线程持有许可证,然后子线程的park方法返回
        LockSupport.unpark(thread);
    }
}

输出结果为:


子线程开始park
主线程开始unPark
子线程结束park


park方法返回时不会告诉调用者因何种原因返回,所以调用者需要根据之前调用park方法的原因,再次检查条件是否满足,如果不满足则还需要再次调用park方法。


例如,根据调用前后中断状态的对比就可以判断是不是因为被中断才返回的。为了说明调用park方法后的线程被中断后返回,我们修改上述代码,删除LockSupport.unpark(thread),然后添加thread.interrupt():


public class LockSupportTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("子线程开始park");
            //只有被中断时退出循环
            while (!Thread.currentThread().isInterrupted()){
                LockSupport.park();//调用park,挂起自己
            }
            System.out.println("子线程结束park");
        });
        //启动子线程
        thread.start();
        //主线程休眠1s
        Thread.sleep(1000);
        thread.interrupt();
    }
}

输出结果:


子线程开始park
子线程结束park


如上代码中,只有中断了子线程,子线程才会运行结束;如果子线程不被中断,即使是调用了upark(thread)方法也无济于事。


3、void parkNanos(long nanos)方法


park()方法不同的是,如果调用void parkNanos(long nanos)方法的线程没有获取到许可证,则调用线程会被挂起nanos时间后自动返回。


4、void park(Object blocker)方法


当线程在没有持有许可证的情况下调用park方法而被阻塞挂起时,这个blocker对象会被记录到该线程内部。

 public static void park(Object blocker) {
    //获取调用线程
        Thread t = Thread.currentThread();
        //设置该线程的blocker变量
        setBlocker(t, blocker);
        //挂起线程
        UNSAFE.park(false, 0L);
        //线程被激活后清除blocker变量,因为一般都是在线程阻塞时才分析原因
        setBlocker(t, null);
    }

Thread类里面有个变量volatile Object parkBlocker,里面用来存放调用park(Object blocker)的blocker对象。


5、void parkNanos(Object blocker, long nanos)方法


比void park(Object blocker)方法多了个nanos超时时间。


6、void parkUntil(Object blocker, long deadline)方法


此方法与void parkNanos(Object blocker, long nanos)方法的区别在于,parkUntil是到达某一个时间点(把这个时间点转换成从1970年到这个时间点的总毫秒数作为参数deadline)后返回;而parkNanos则是经过nanos的时间后返回。


7、先进先出锁的实现例子


这是一个先进先出锁的实现,也就是只有队列的首元素可以获取锁:

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
public class FIFOMutex {
    private final AtomicBoolean locked = new AtomicBoolean(false);
    private final Queue<Thread> waiters = new ConcurrentLinkedDeque<>();
    public void lock() {
        boolean wasInterrupted = false;
        Thread currentThread = Thread.currentThread();
        waiters.offer(currentThread);
        //(1)
        while (waiters.peek() != currentThread || !locked.compareAndSet(false, true)) {
            LockSupport.park(this);
            //(2)
            if (Thread.interrupted()) {
                wasInterrupted = true;
            }
        }
        waiters.poll();
        //(3)
        if (wasInterrupted) {
            currentThread.interrupt();
        }
    }
    public void unlock() {
        locked.set(false);
        LockSupport.unpark(waiters.peek());
    }
}


代码(1),如果当前线程不是队头或者当前锁已经被其他线程获取,则调用park方法挂起自己。

代码(2),如果park方法是因为被中断而返回,则忽略中断,并重置中断标志,做个标记,然后再次判断当前线程是不是队头或者当前锁已经被其他线程获取,如果是则继续调用park方法挂起自己。

代码(3),判断标记,如果标记为true则中断该线程。


总结


本期主要学习了LockSupport的知识点以及其常用的parkunpark的方法,另外通过一个先进先出锁的例子来加深了对前面知识的理解。LockSupport类是创建锁和其他同步类的基础,了解了它会对后续的学习更有帮助。下期预告:抽象同步队列AQS

相关文章
|
10天前
|
JSON 人工智能 测试技术
告别硬编码断言!基于Skills的接口测试,智能体自动组合请求与校验(附代码)
接口测试常陷“脚本地狱”:字段一改,满屏硬编码断言全崩。AI生成也难解耦,效率反被维护吞噬。本文提出“Skills”模块化校验方案——将状态码、字段存在性等校验逻辑封装为可复用技能,由智能体依接口契约自动组合执行。解耦断言与脚本,让测试真正随业务演进。
|
10月前
正则化方法、L曲线和奇异值分解
正则化方法、L曲线和奇异值分解
|
11月前
|
网络协议 API PHP
域名WHOIS信息查询免费API使用指南
本文介绍接口盒子提供的免费域名WHOIS查询API,支持获取域名注册信息、到期时间、DNS服务器等数据。开发者可通过GET或POST请求调用,需使用用户ID和通讯秘钥认证,适用于.com、.net等顶级域名。提供PHP和Python调用示例及注意事项。
693 0
|
机器学习/深度学习 搜索推荐 算法
在数字化时代,推荐系统成为互联网应用的重要组成部分,通过机器学习技术根据用户兴趣和行为提供个性化推荐,提升用户体验
在数字化时代,推荐系统成为互联网应用的重要组成部分,通过机器学习技术根据用户兴趣和行为提供个性化推荐,提升用户体验。本文探讨了推荐系统的基本原理、常用算法、实现步骤及Python应用,介绍了如何克服数据稀疏性、冷启动等问题,强调了合理选择算法和持续优化的重要性。
678 4
|
Java
Java控制电脑给对方微信发消息
本来打算搞个舔狗日记和暖心话术得....
289 0
|
存储 缓存 算法
[尚硅谷flink] 检查点笔记
[尚硅谷flink] 检查点笔记
760 3
|
网络协议 数据安全/隐私保护
|
存储 数据采集 监控
Flume 拦截器概念及自定义拦截器的运用
Apache Flume 的拦截器是事件处理组件,位于Source和Channel之间,用于在写入Channel前对数据进行转换、提取或删除。它们支持数据处理和转换、数据增强、数据过滤以及监控和日志功能。要创建自定义拦截器,需实现Interceptor接口,包含initialize、intercept、intercept(List&lt;Event&gt;)和close方法。配置拦截器时,通过Builder模式实现Interceptor.Builder接口。在Flume配置文件中指定拦截器全类名,如`TestInterceptor$Builder`,然后启动Flume进行测试。
859 0
|
人工智能 Python
【AI大模型应用开发】【LangChain系列】加速学习LangChain效率:源码环境安装 + 断点调试
【AI大模型应用开发】【LangChain系列】加速学习LangChain效率:源码环境安装 + 断点调试
445 0