• Java进阶篇--LockSupport


    目录

    LockSupport简介

    LockSupport方法介绍

    代码示例


    LockSupport简介

    LockSupport位于java.util.concurrent.locks包下,可以用来实现线程的阻塞和唤醒操作。每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在线程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用unpark()方法使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。

    LockSupport方法介绍

    LockSupport类提供了阻塞和唤醒线程的方法,主要有以下几个方法:

    阻塞线程

    1. void park(): 阻塞当前线程,如果调用unpark方法或者当前线程被中断,则从park()方法中返回。
    2. void park(Object blocker): 功能同方法1,但增加了一个Object参数,用于记录导致线程阻塞的阻塞对象,方便问题排查。
    3. void parkNanos(long nanos): 阻塞当前线程,最长不超过nanos纳秒,具有超时返回的特性,如果nanos为0,则会立即返回。
    4. void parkNanos(Object blocker, long nanos): 功能同方法3,增加了记录阻塞对象的参数。
    5. void parkUntil(long deadline): 阻塞当前线程,直到deadline时间点,以毫秒表示。
    6. void parkUntil(Object blocker, long deadline): 功能同方法5,增加了记录阻塞对象的参数。

    唤醒线程

    1. void unpark(Thread thread): 唤醒处于阻塞状态的指定线程。

    需要注意的是,LockSupport的阻塞和唤醒线程功能是依赖于sun.misc.Unsafe类实现的。在阻塞线程方法中,可以使用带Object参数的重载方法来记录导致线程阻塞的阻塞对象,这有助于问题排查和定位。

    相较于传统的synchronized等同步方式,LockSupport的线程阻塞不会引发死锁和竞争条件问题,并且可以精确地控制线程的阻塞和唤醒。当线程被阻塞时,其状态会变为WAITING;而使用synchronized等方式阻塞线程时,线程的状态则会变为BLOCKED。

    代码示例

    以下是一个示例,展示了如何使用 LockSupport 和条件变量(Condition)来实现生产者-消费者模式:

    1. import java.util.LinkedList;
    2. import java.util.Queue;
    3. import java.util.concurrent.locks.Condition;
    4. import java.util.concurrent.locks.Lock;
    5. import java.util.concurrent.locks.ReentrantLock;
    6. import java.util.concurrent.locks.LockSupport;
    7. public class main {
    8. private static final int CAPACITY = 5;
    9. private static Queue queue = new LinkedList<>();
    10. private static Lock lock = new ReentrantLock();
    11. private static Condition notFull = lock.newCondition();
    12. private static Condition notEmpty = lock.newCondition();
    13. public static void main(String[] args) {
    14. Thread producer = new Thread(() -> {
    15. for (int i = 0; i < 10; i++) {
    16. lock.lock();
    17. try {
    18. while (queue.size() == CAPACITY) {
    19. notFull.await();
    20. }
    21. queue.offer(i);
    22. System.out.println("生产者生产:" + i);
    23. notEmpty.signalAll();
    24. } catch (InterruptedException e) {
    25. e.printStackTrace();
    26. } finally {
    27. lock.unlock();
    28. }
    29. }
    30. });
    31. Thread consumer = new Thread(() -> {
    32. while (true) {
    33. lock.lock();
    34. try {
    35. while (queue.isEmpty()) {
    36. notEmpty.await();
    37. }
    38. int item = queue.poll();
    39. System.out.println("消费者消费:" + item);
    40. notFull.signalAll();
    41. } catch (InterruptedException e) {
    42. e.printStackTrace();
    43. } finally {
    44. lock.unlock();
    45. }
    46. }
    47. });
    48. producer.start();
    49. consumer.start();
    50. // 让主线程等待一段时间
    51. try {
    52. Thread.sleep(5000);
    53. } catch (InterruptedException e) {
    54. e.printStackTrace();
    55. }
    56. // 停止生产者和消费者线程
    57. LockSupport.unpark(producer);
    58. LockSupport.unpark(consumer);
    59. }
    60. }

    在以上代码中,queue 是一个用于存储数据的队列,lock 是一个可重入锁,notFull 和 notEmpty 分别是条件变量,用于控制生产者和消费者的等待和唤醒操作。

    生产者线程在每次生产之前,使用 lock.lock() 获取锁并进入临界区,然后通过 while 循环检查队列是否已满。如果队列已满,则调用 notFull.await() 将生产者线程阻塞,直到有消费者消费数据并唤醒生产者线程。一旦队列有空位,生产者将数据放入队列,并调用 notEmpty.signalAll() 唤醒等待的消费者线程。最后,释放锁并结束本次生产。

    消费者线程在每次消费之前,也需要先获取锁并进入临界区,然后通过 while 循环检查队列是否为空。如果队列为空,则调用 notEmpty.await() 将消费者线程阻塞,直到有生产者生产数据并唤醒消费者线程。一旦队列有数据,消费者从队列中取出数据,并调用 notFull.signalAll() 唤醒等待的生产者线程。最后,释放锁并继续下一轮消费。

    主线程启动生产者和消费者线程后,等待一段时间后调用 LockSupport.unpark(Thread) 方法来停止生产者和消费者线程。

    这个示例展示了如何使用 LockSupport 和条件变量实现生产者-消费者模式,确保生产者在队列满时等待,消费者在队列空时等待,并通过条件变量进行线程的唤醒操作。这种方式可以有效地保证生产者和消费者的同步和互斥,避免了普通的忙等待和竞争条件,提高了系统的效率和可靠性。

  • 相关阅读:
    LiveGBS流媒体平台GB/T28181功能-视频直播流快照的安全控制配置播放回调鉴权接口控制播放权限
    C# 根据前台传入实体名称,动态查询数据
    构建工具Webpack简介
    Python 基础入门指南,干货分享来啦!
    jemter ramp-up
    2022年武汉专精特新小巨人企业奖励补贴以及申报条件汇总
    【深度学习】从LeNet-5识别手写数字入门深度学习
    XML配置文件解析与建模
    在antd里面渲染MarkDown并且自定义一个锚点目录TOC(重点解决导航目录不跟随文档滚动的问题)
    Python---类的定义和使用语法
  • 原文地址:https://blog.csdn.net/m0_74293254/article/details/133993101