一文读懂LockSupport

这篇具有很好参考价值的文章主要介绍了一文读懂LockSupport。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

阅读本文前,需要储备的知识点如下,点击链接直接跳转。
java线程详解
Java不能操作内存?Unsafe了解一下

LockSupport介绍

搞java开发的基本都知道J.U.C并发包(即java.util.concurrent包),所有并发相关的类基本都来自于这个包下,这个包是JDK1.5以后由祖师爷Doug Lea写的,LockSupport也是在这时诞生的,在JDK1.6又加了些操作方法。
其实LockSupport的这些静态方法基本都是调用Unsafe类的方法,所以建议大家看看文章开头的Unsafe那篇文章。
首先我们来看看LockSupport类开头的一段注释。

/**
 * Basic thread blocking primitives for creating locks and other
 * synchronization classes.
 *
 * <p>This class associates, with each thread that uses it, a permit
 * (in the sense of the {@link java.util.concurrent.Semaphore
 * Semaphore} class). A call to {@code park} will return immediately
 * if the permit is available, consuming it in the process; otherwise
 * it <em>may</em> block.  A call to {@code unpark} makes the permit
 * available, if it was not already available. (Unlike with Semaphores
 * though, permits do not accumulate. There is at most one.)

大概的意思就是说,LockSupport这个类用于创建锁和其他同步类的基本线程阻塞原语。这个类与使用它的每个线程关联一个许可证,这个许可证数量不会累积。最多只有一个即permit要么是0要么是1,如果调用park方法,permit=1时则当前线程继续执行,否则没有获取到许可证,阻塞当前线程;调用unpark方法会释放一个许可,把permit置为1,连续多次调用unpark只会把许可证置为1一次,被阻塞的线程获取许可后继续执行。
额,可能刚开始接触这个类的童鞋有点懵逼,不过没关系,下面我为大家准备了饮料小菜花生米,诸位搬好小板凳,静静的听我吹牛逼吧,哈哈。

API

LockSupport类只有几个静态方法,构造方法是私有的,所以使用的过程中就调用它的这几个静态方法就够了。

  • 单纯的设置和获取阻塞对象。
// 给线程t设置阻塞对象为arg,以便出问题时排查阻塞对象,这个方法为私有方法,其他park的静态方法会调用这个方法设置blocker
private static void setBlocker(Thread t, Object arg)
// 获取线程t的阻塞对象,一般用于排查问题
public static Object getBlocker(Thread t)
  • 单纯的阻塞和给线程释放许可
// 阻塞当前线程,如果已经获取到许可则不阻塞继续执行,这个阻塞可以响应中断
public static void park()
// 释放线程thread的许可,使得thread线程从park处继续向后执行,如果threa为null不做任何操作
public static void unpark(Thread thread)
  • 只带时间的阻塞
// 阻塞线程,设置了等待超时时间,单位是纳秒,是相对时间,nanos<=0不会阻塞,相当于没有任何操作;nanos>0时,如果等待时间超过nanos纳秒还没有获取到许可,那么线程自动恢复执行
public static void parkNanos(long nanos)
// 这里的deadline单位是毫秒,而且是绝对时间,调用后会阻塞到指定的绝对时间如果还没有获取到许可则自动恢复执行
public static void parkUntil(long deadline)
  • 同时带阻塞对象和时间的阻塞
// 默认的许可permit=0,阻塞当前线程,并设置阻塞对象为blocker其实就是调用setBlocker这个私有方法。如果当前线程的permit=1了那么再调park是不会阻塞的,因为可以获取到许可继续执行。当前线程获取到许可后会清除blocker为null
public static void park(Object blocker)
// 作用同park(Object blocker)方法,唯一的区别就是设置了等待超时时间,单位是纳秒,是相对时间,nanos<=0不会阻塞,相当于没有任何操作;nanos>0时,如果等待时间超过nanos纳秒还没有获取到许可,那么线程自动恢复执行,例如nanos=1000*1000*1000,这个相当于1秒,等到1秒后如果还没有获取到许可醒则自动恢复
public static void parkNanos(Object blocker, long nanos)
// 作用同parkNanos(Object blocker, long nanos),设置阻塞对象blocker,但是这里的deadline单位是毫秒,而且是绝对时间,调用了parkUntil后会阻塞到指定的绝对时间如果还没有获取到许可则自动恢复执行
public static void parkUntil(Object blocker, long deadline)
  • 其他(别问,问我也不知道)
// 返回伪随机初始化或更新的辅助种子。由于包访问限制,从ThreadLocalRandom复制。PS:这是百度翻译的,平时用得少,我也没用过,暂且先放这里吧,用到了再细讲
static final int nextSecondarySeed()

关于LockSupport的park相关方法阻塞,有以下三种方法可获取到许可并继续向后执行。

  1. 主动调用unpark(Thread thread)方法,使得线程获得许可继续执行。
  2. 中断该线程即调用interrupt()方法,调用后线程不会抛出异常,直接从park的地方恢复过来继续执行
  3. 无原因的虚拟的返回,这种情况目前没有遇到过,不过在java.util.concurrent.locks.LockSupport.park()的注释里会有这种情况

使用案例

  • 基础的阻塞和释放许可
public static void test1() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 调用park()方法会一直阻塞直到获得permit或者被中断
            LockSupport.park();
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " get permit");
        }
    }, "t1");
    t.start();
    Thread.sleep(2000);
    // 使得被阻塞的线程继续执行有三种方法
    // 1、主动调用unpark(Thread thread)方法,使得线程获得许可继续执行
    LockSupport.unpark(t);
    // 2、中断该线程即调用interrupt()方法,调用后线程不会抛出异常,直接从park的地方恢复过来继续执行
    // t.interrupt();
    // 3、无原因的虚拟的返回,这种情况目前没有遇到过,不过在java.util.concurrent.locks.LockSupport.park()的注释里会有这种情况
}

输出如下:

2020-05-19 20:25:16:t1 is running...
2020-05-19 20:25:18:t1 get permit
  • 设置和获取阻塞对象
public static void test2() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 设置线程的阻塞对象为一个字符串
            LockSupport.park("i am blocker");
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " get permit");
        }
    }, "t1");
    t.start();
    Thread.sleep(2000);
    // 获取t线程的阻塞对象,如果没有设置线程t的阻塞对象,则获取到的blocker是null
    Object blocker = LockSupport.getBlocker(t);
    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":" + Thread.currentThread().getName()
        + " get block class type:" + blocker.getClass());
    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":" + Thread.currentThread().getName()
        + " get block value toString:" + blocker);
    LockSupport.unpark(t);
}

输出:

2020-05-19 20:32:00:t1 is running...
2020-05-19 20:32:02:main get block class type:class java.lang.String
2020-05-19 20:32:02:main get block value toString:i am blocker
2020-05-19 20:32:02:t1 get permit
  • 带相对和绝对时间的阻塞
public static void test3() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 设置线程的阻塞对象为一个字符串,并且阻塞3s,这里是相对时间,如有没有被unpark或者线程中断,3s后自动恢复执行
            LockSupport.parkNanos("block1", TimeUnit.SECONDS.toNanos(3));
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " continue...");
        }
    }, "t1");
    t1.start();
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 设置线程的阻塞对象为一个字符串,并且阻塞5s,这里使用的是绝对时间,只到当前时间+5s转换为毫秒,如有没有被unpark或者线程中断,绝对时间到后自动恢复执行
            LockSupport.parkUntil("block2", System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(5));
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " continue...");
        }
    }, "t2");
    t2.start();
}

输出结果:

2020-05-20 08:35:07:t2 is running...
2020-05-20 08:35:07:t1 is running...
2020-05-20 08:35:10:t1 continue...
2020-05-20 08:35:12:t2 continue...
关于park阻塞的方法,针对于阻塞时间总结一下,有三种使用情况情况。
- 无限期阻塞,不带任何时间相关的参数,这种底层调用的是UNSAFE.park(false, 0L)。
- 相对时间阻塞,调用的parkNanos相关方法,这里的时间参数是一个相对时间,单位是纳秒,这种底层调用的是UNSAFE.park(false, nanos),表示经过nanos纳秒后如果还未获取到许可则自动恢复执行。
- 绝对时间阻塞,调用的parkUntil相关方法,这里的时间参数是一个绝对时间,单位是毫秒,这种底层调用的是UNSAFE.park(true, deadline),表示把当前时间换算成毫秒,如果值等于deadline毫秒后未获取到许可则自动恢复执行。

与对象锁比较

LockSupport与对象锁主要区别如下:

  1. 关注维度不同
    LockSupport是针对于线程级别的,而对象锁是synchronized关键字配合object对象的notify()、notifyAll()和wait()方法使用的,这种是针对于对象级别的。两者阻塞方式不同,我们看个栗子吧。
public static void test4() throws Exception {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            LockSupport.park();
        }
    }, "t1");
    t1.start();
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Object obj = new Object();
                synchronized (obj) {
                    obj.wait();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "t2");
    t2.start();
}

上面这段代码创建了两个线程,t1使用LockSupport.park()阻塞,t2使用obj.wait()阻塞,调用这个方法执行后,我们看看jvm的线程信息。

  • 先用jps找到对应的进程
    一文读懂LockSupport

  • 使用jstack查看线程信息
    一文读懂LockSupport

从这个dump的线程堆栈信息我们可以看出,t1和t2线程都处于WATING状态,但是t1是阻塞在了Unsafe.park方法上,parking状态,等待获取许可,t2是阻塞在Object.wait方法上,在等待一个object monitor即对象锁。
2. 唤醒方式不同
LockSupport是唤醒指定的线程,而notify()或者notifyAll()无法指定要唤醒的线程,只是表明对象上的锁释放了,让其他等待该锁的线程继续竞争锁,至于哪个线程先获取到锁是随机的,只是将获取到锁的线程由阻塞等待状态变成就绪状态,等待操作系统的调度才能继续执行。
3. 使用方式不同
LockSupport的park阻塞方式是在当前线程中执行并阻塞当前线程,但是唤醒unpark方法是在其他线程中执行的,并且唤醒后被park阻塞的方法能立即继续执行。但是notify或者notifyAll方法虽然调用后起到了通知释放对象锁的作用,但是他必须退出synchronized后才生效,下面我们分别看两个栗子。
LockSupport的park和unpark

public static void test5() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread lockSupportThread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is go parking...");
            LockSupport.park();
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " continue...");
        }
    }, "lockSupportThread1");
    // 让lockSupportThread1线程先执行起来
    lockSupportThread1.start();
    Thread lockSupportThread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                    + Thread.currentThread().getName() + " is running...");
                // 让当前线程休眠1s
                Thread.sleep(1000);
                // unpark线程lockSupportThread1
                LockSupport.unpark(lockSupportThread1);
                // 让当前线程休眠3s
                Thread.sleep(3000);
                System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                    + Thread.currentThread().getName() + " over...");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "lockSupportThread2");
    lockSupportThread2.start();
}

输出:

2020-05-21 12:11:27:lockSupportThread2 is running...
2020-05-21 12:11:27:lockSupportThread1 is go parking...
2020-05-21 12:11:28:lockSupportThread1 continue...
2020-05-21 12:11:31:lockSupportThread2 over...

这里我们看到lockSupportThread2线程调用LockSupport.unpark后,虽然有休眠,但是lockSupportThread1线程还是立即执行了,说明LockSupport.unpark是立即释放线程许可。
接下来我们看下Object的wait()和notifyAll()。

public static void test6() {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Object object = new Object();
    Thread lockSupportThread3 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (object) {
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " is running...");
                    // 释放object锁让其他线程可以获得,当前线程阻塞
                    object.wait();
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " over...");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "lockSupportThread3");
    lockSupportThread3.start();
    Thread lockSupportThread4 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // 让当前线程休眠2s,确保lockSupportThread3先获取到object锁
                Thread.sleep(2000);
                synchronized (object) {
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " is running...");
                    // 让当前线程休眠1s
                    Thread.sleep(1000);
                    // 唤醒等待在object锁上的线程
                    object.notifyAll();
                    // 让当前线程休眠3s
                    Thread.sleep(3000);
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " over...");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "lockSupportThread4");
    lockSupportThread4.start();
}

输出结果:

2020-05-21 12:16:51:lockSupportThread3 is running...
2020-05-21 12:16:53:lockSupportThread4 is running...
2020-05-21 12:16:57:lockSupportThread4 over...
2020-05-21 12:16:57:lockSupportThread3 over...

lockSupportThread4先休眠了2s确保lockSupportThread3先执行并获取到object对象锁,然后lockSupportThread3调用了object.wait(),释放object锁并线程阻塞等待,然后lockSupportThread4获取到了object锁继续执行,虽然lockSupportThread4在休眠和打印输出前调用了notifyAll方法,但是依然是lockSupportThread4的同步块代码执行完成后lockSupportThread3才开始执行。

总结

本文中虽然我们只介绍了LockSupport的API方法和使用案例,其实这也是除synchronized结合Object的wait()、notify()、notifyAll()来协调多线程同步的另一种方式。而且在只协调多线程的的情况下LockSupport会显得更灵活。
另外在jdk的并发包下,有各种锁,比如ReentrantLockCountDownLatchCyclicBarrier等,只要往底层看下源码,可以发现他们都使用了AbstractQueuedSynchronizer(简称AQS,抽象队列同步器,后续文章会专门介绍),而AbstractQueuedSynchronizer里的线程阻塞和唤醒正是使用的就是LockSupport,所以想要搞懂原理,就得把这些一一梳理清楚,最后自然而然就明白了。

说到这里,让我突然想起张三丰教张无忌学太极时的那一段对话。

张三丰:“无忌,我教你的还记得多少?”
张无忌:“回太师傅,我只记得一大半”
张三丰:“ 那,现在呢?”
张无忌:“已经剩下一小半了”
张三丰:“那,现在呢?”
张无忌:“我已经把所有的全忘记了!”
张三丰:“好,忘了好,刚才教你的都是错的,重新来吧...”
张无忌:......

emmmmm,好像走错片场了,那就江湖再见吧。。。文章来源地址https://www.toymoban.com/news/detail-655592.html

到了这里,关于一文读懂LockSupport的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一文读懂“大语言模型”

    本文基于谷歌云的官方视频:《Introduction to Large Language Models》 ,使用 ChatGPT4 整理而成,希望对大家入门大语言模型有帮助。 本课程主要包括以下 4 方面的内容: 大语言模型的定义 描述大语言模型的用例 解释提示词调优 谷歌的 Gen AI 开发工具介绍 大语言模型是深度学习的

    2024年02月09日
    浏览(66)
  • 一文读懂数据加密

    在计算机信息安全领域,之前软件设计师的网络安全部分了解了一点密码学的知识,这里随想记录一下。 数据加密 的基本过程就是对原来为 明文 的文件或数据按 某种算法 进行处理,使其成为不可读的一段代码为“ 密文 ”,使其只能在输入相应的 密钥 之后才能显示出原容

    2024年02月03日
    浏览(37)
  • 一文读懂Docker原理

    说起Docker,基本上就是指容器。许多同学熟悉Docker的操作,却搞不懂到底什么是容器。本文就来讲讲Docker容器到底是个啥。 容器被称为轻量级的虚拟化技术,实际上是不准确的。确切地说,容器是一种对进程进行隔离的运行环境。 由于生产环境的容器几乎都是运行在Linux上的

    2024年01月17日
    浏览(41)
  • 一文读懂Websocket

    提示:通过本章节的学习,你想深入了解什么是websocket。 提示:这里可以添加本文要记录的大概内容: 例如:websocket是html5新增的全双工通讯协议,基于Tcp协议,属于应用层协议。 提示:以下是本篇文章正文内容,下面案例可供参考 WebSocket 是一种在单个 TCP 连接上进行全双

    2024年02月16日
    浏览(44)
  • 一文读懂Embedding

    “Embedding”直译是 嵌入式、嵌入层 。 简单来说,我们常见的 地图 就是对于 现实地理的Embedding ,现实的地理地形的信息其实远远超过三维,但是地图通过颜色和等高线等来最大化表现现实的地理 信息 。 通过它,我们在现实世界里的文字、图片、语言、视频就能转化为计算

    2024年02月11日
    浏览(43)
  • 一文读懂Redis哨兵

    吹哨人巡查监控后台master主机是否故障,如果故障了根据 投票数 自动将某一个从库转换为新主库,继续对外服务。 俗称,无人值守运维。 主从监控:监控主从redis库运行是否正常 消息通知:哨兵可以将故障转移的结果发送给客户端 故障转移:将其中一个Slave作为新的Maste

    2024年02月04日
    浏览(33)
  • 一文读懂HTML

    HTML(HyperText Markup Language)的历史可以追溯到20世纪90年代早期,它是互联网发展的重要里程碑之一。以下是HTML的历史概述: 早期阶段(1980年代末 - 1990年代初):在互联网的早期阶段,人们开始意识到需要一种标记语言来创建和共享文档。这导致了Tim Berners-Lee在1989年至1991年

    2024年02月13日
    浏览(39)
  • 一文读懂ChatGPT

    ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI 研发的聊天机器人程序,于2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完

    2024年02月06日
    浏览(53)
  • 一文读懂c++语言

    C++是一种通用的、高级的编程语言,它是C语言的扩展。C++由Bjarne Stroustrup于1983年首次引入,并在之后的几十年中不断发展壮大。C++被广泛应用于各种领域,包括系统开发、游戏开发、嵌入式系统、图形用户界面(GUI)开发等。 C++的设计目标是提供一种高效、灵活和可扩展的

    2024年02月13日
    浏览(37)
  • 一文读懂Zookeeper

    1、介绍zookeeper 1.1、zookeeper概述 ​ ZooKeeper从字面意思理解,【Zoo - 动物园,Keeper - 管理员】动物园中有很多种动物,这里的动物就可以比作分布式环境下多种多样的服务,而ZooKeeper做的就是管理这些服务。 ​ Apache ZooKeeper的系统为分布式协调是构建分布式应用的高性能服务。

    2024年02月10日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包