探索Java并发编程利器:LockSupport,一种高效的线程阻塞与唤醒机制

简介: LockSupport 是 Java SE 9 及以上版本中引入的一个线程同步工具类,用于支持同步方法,提供了多种同步机制.LockSupport 所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。LockSupport 底层调用的是Unsafe中的native代码。java.util.concurrent并发包下很多并发类的底层加锁都是基于LockSupport,如ReentrantLock、CountDownLatch、ParkableLazyDeque 等。
关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

一、导读

我们继续总结学习Java基础知识,温故知新。

二、概览

LockSupport 是 Java SE 9 及以上版本中引入的一个线程同步工具类,用于支持同步方法,提供了多种同步机制.

LockSupport 所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。
LockSupport 底层调用的是Unsafe中的native代码。

java.util.concurrent并发包下很多并发类的底层加锁都是基于LockSupport,如ReentrantLock、CountDownLatch、ParkableLazyDeque 等。

三、用法

LockSupport 类中最常用的方法是 Park() 和 Unpark() 方法,分别用于阻塞和唤醒线程。


       Thread a = new Thread(() -> {

           System.out.println("start " + Thread.currentThread().getName());
           int resule = 0;
           for(int i = 0; i < 10; i ++) {
               resule += i;
           }
           // 阻塞线程
           LockSupport.park();
           System.out.println(resule);

       }, "a");

       a.start();
       try {
//            TimeUnit.MILLISECONDS.sleep(1000);
       } catch (Exception e) {
       }
       
       // 释放,这里不调用的话,上面将一直锁定
       LockSupport.unpark(a);
AI 代码解读

四、原理

park和unpark都是直接调用了Unsafe的方法,
我们这么理解:这两个方法的原理是基于一个计数器,计数器的值决定了线程的阻塞等待状态。当计数器的值减为 0 时,表示线程可以被唤醒,执行相应的操作。

此外,LockSupport 还提供了一些同步方法,如 Lock 和 Unlock() 方法,用于获取和释放锁。Lock 方法用于获取锁,Unlock() 方法用于释放锁。与 synchronized 关键字不同,Lock 和 Unlock() 方法不需要同时进行,可以在任意一个方法中获取锁,并且可以在任意一个方法中释放锁,因此更加灵活和方便。


    调用park后,此时线程处于waiting的状态
    
    public static void park() {
        UNSAFE.park(false, 0L);
    }
    
    public static void park(Object blocker) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        
        // 设置Blocker
        setBlocker(t, blocker);
        
        // 此后,当前线程就已经阻塞了,后面的函数无法运行,直至unpark函数被调用
        UNSAFE.park(false, 0L);
        
        //unpark函数被调用后,继续执行,如果没有第二个setBlocker,那么之后没有调用park(Object blocker),
        //而直接调用getBlocker函数,得到的还是前一个park(Object blocker)设置的blocker,显然是不符合逻辑的
        setBlocker(t, null);
    }
    
    public static void unpark(Thread thread) {
        if (thread != null)
           UNSAFE.unpark(thread);
    }
AI 代码解读

Unsafe.java


    public native void unpark(Object var1);

    public native void park(boolean var1, long var2);
AI 代码解读

五、线程等待和唤醒的方法

5.1 LockSupport.park()

    // Hotspot implementation via intrinsics API
    private static final sun.misc.Unsafe UNSAFE;
    
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            
        } catch (Exception ex) { throw new Error(ex); }
    }
AI 代码解读

Thread.sleep()和LockSupport.park()的区别

Thread.sleep()和LockSupport.park() 都是阻塞当前线程的执行,
都 不会 释放 当前线程占有的锁资源;

  • Thread.sleep()没法从外部唤醒,只能自己醒过来;本身就是一个native方法;
  • LockSupport.park()方法可以被另一个线程调用LockSupport.unpark()方法唤醒;* 底层是调用的Unsafe的native方法;

Thread.sleep()方法需要捕获中断异常。
LockSupport.park()不需要捕获中断异常。

5.2 Object中的wait()、notify、notifyAll

Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在sychronized内部执行

Object.wait()原理

在 Object.wait() 方法中,首先使用 synchronized 关键字锁定当前对象,然后调用 Object.wait() 方法等待另一个线程执行完毕并返回一个 Future 对象。在等待过程中,当前线程会一直阻塞,直到另一个线程执行完毕并返回一个 Future 对象。当等待线程执行完毕后,Future 对象的 get() 方法将返回等待线程的结果。因此,在使用 Object.wait() 方法时,需要确保等待线程已经执行完毕,否则可能会导致死锁等问题。

5.3 Condition的await()方法

Java中的Condition是java.util.concurrent.locks包下的一个接口,它主要用于在多线程环境中控制线程的等待和唤醒。

线程需要获得并持有锁,必须在锁块(synchronized或lock中)
必须要先等待后唤醒,线程才能够被唤醒。

5.4 Thread.sleep()和Object.wait()的区别

Thread.sleep()不会释放锁资源,到时间了会自动唤醒,然后继续执行;
Object.wait()会释放锁资源。需要另一个线程使用Object.notify()唤醒

5.5 Object.wait()和LockSupport.park()的区别

二者都会阻塞当前线程的运行。

  • Object.wait()方法需要在synchronized块中执行;
  • LockSupport.park()可以在任意地方执行;
  • Object.wait()方法需要捕获中断异常。
  • LockSupport.park()不需要捕获中断异常。
  • Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
  • LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;

5.6 Object.wait()和Condition.await()的区别

Object.wait()和Condition.await()的原理是基本一致的,不同的是Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。

5.7 java中,notify/wait等方法为什么要在同步代码块中执行

同步代码块通常由 synchronized 关键字修饰
在Java中,notify() 和 wait() 方法通常用于实现线程之间的通信和同步。这些方法通常在同步代码块中执行,以确保线程安全和原子性。
在同步代码块中执行 notify() 和 wait() 方法可以确保线程安全和原子性,因为它们可以保证在同一时刻只有一个线程可以执行这些方法。如果在非同步代码块中执行这些方法,则可能会导致竞争条件和死锁等问题。

5.8 如果在wait()之前执行了notify()会怎样?

如果当前的线程不是此对象锁的所有者,却调用该对象的notify()或wait()方法时抛出IllegalMonitorStateException异常;
如果当前线程是此对象锁的所有者,wait()将一直阻塞,因为后续将没有其它notify()唤醒它。

5.8 如果在park()之前执行了unpark()会怎样?

线程不会被阻塞,直接跳过park(),继续执行后续内容,大家可以自己尝试下


        Thread a = new Thread(() -> {

            System.out.println("start " + Thread.currentThread().getName());
            int resule = 0;
            for(int i = 0; i < 10; i ++) {
                resule += i;
            }
            LockSupport.park();
            System.out.println(resule);

        }, "a");

        try {
//            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (Exception e) {
        }
        LockSupport.unpark(a);
        a.start();
        LockSupport.unpark(a);
AI 代码解读

六、 推荐阅读

Java 专栏

[SQL 专栏]

[数据结构与算法]

[Android学习专栏]

目录
打赏
0
0
0
0
46
分享
相关文章
|
7天前
|
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
45 17
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
17天前
|
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
Java 多线程 面试题
Java 多线程 相关基础面试题
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
42 3
|
20天前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
105 2
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
28天前
|
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
48 6
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等