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

简介: LockSupport 是 Java SE 9 及以上版本中引入的一个线程同步工具类,用于支持同步方法,提供了多种同步机制.LockSupport 所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。LockSupport 底层调用的是Unsafe中的native代码。java.util.concurrent并发包下很多并发类的底层加锁都是基于LockSupport,如ReentrantLock、CountDownLatch、ParkableLazyDeque 等。

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

@TOC

一、导读

我们继续总结学习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);

四、原理

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);
    }

Unsafe.java


    public native void unpark(Object var1);

    public native void park(boolean var1, long var2);

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

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); }
    }

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);

六、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

相关文章
|
4天前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
19小时前
|
安全 Java 程序员
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第20天】本文将深入探讨Java并发编程的核心概念,包括线程安全和性能优化。我们将详细解析synchronized关键字、ReentrantLock类以及java.util.concurrent包中的高级工具类,如Semaphore、CountDownLatch和CyclicBarrier等。通过实例演示如何使用这些工具来提高多线程程序的性能和可靠性。
|
20小时前
|
安全 Java 开发者
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第20天】在Java并发编程中,线程安全和性能优化是两个关键要素。本文将深入探讨Java并发编程的基本概念、线程安全的实现方法以及性能优化技巧。通过分析同步机制、锁优化、无锁数据结构和并发工具类的使用,我们将了解如何在保证线程安全的前提下,提高程序的性能。
|
1天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第20天】 在Java开发中,正确处理并发问题对于确保应用的稳定性和提高性能至关重要。本文将深入探讨Java并发编程的核心概念——线程安全,以及如何通过各种技术和策略实现它,同时保持甚至提升系统性能。我们将分析并发问题的根源,包括共享资源的竞争条件、死锁以及线程活性问题,并探索解决方案如同步机制、锁优化、无锁数据结构和并发工具类等。文章旨在为开发者提供一个清晰的指南,帮助他们在编写多线程应用时做出明智的决策,确保应用的高效和稳定运行。
|
2天前
|
Java
Java一分钟之-并发编程:线程间通信(Phaser, CyclicBarrier, Semaphore)
【5月更文挑战第19天】Java并发编程中,Phaser、CyclicBarrier和Semaphore是三种强大的同步工具。Phaser用于阶段性任务协调,支持动态注册;CyclicBarrier允许线程同步执行,适合循环任务;Semaphore控制资源访问线程数,常用于限流和资源池管理。了解其使用场景、常见问题及避免策略,结合代码示例,能有效提升并发程序效率。注意异常处理和资源管理,以防止并发问题。
24 2
|
2天前
|
安全 Java 容器
Java一分钟之-并发编程:线程安全的集合类
【5月更文挑战第19天】Java提供线程安全集合类以解决并发环境中的数据一致性问题。例如,Vector是线程安全但效率低;可以使用Collections.synchronizedXxx将ArrayList或HashMap同步;ConcurrentHashMap是高效线程安全的映射;CopyOnWriteArrayList和CopyOnWriteArraySet适合读多写少场景;LinkedBlockingQueue是生产者-消费者模型中的线程安全队列。注意,过度同步可能影响性能,应尽量减少共享状态并利用并发工具类。
16 2
|
2天前
|
Java 程序员 调度
Java中的多线程编程:基础知识与实践
【5月更文挑战第19天】多线程编程是Java中的一个重要概念,它允许程序员在同一时间执行多个任务。本文将介绍Java多线程的基础知识,包括线程的创建、启动和管理,以及如何通过多线程提高程序的性能和响应性。
|
2天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【5月更文挑战第18天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过实例分析,我们将看到线程池如何提高系统性能,减少资源消耗,并提高系统的响应速度。
13 5
|
2天前
|
消息中间件 安全 Java
理解Java中的多线程编程
【5月更文挑战第18天】本文介绍了Java中的多线程编程,包括线程和多线程的基本概念。Java通过继承Thread类或实现Runnable接口来创建线程,此外还支持使用线程池(如ExecutorService和Executors)进行更高效的管理。多线程编程需要注意线程安全、性能优化和线程间通信,以避免数据竞争、死锁等问题,并确保程序高效运行。
|
2天前
|
安全 Java 容器
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第18天】随着多核处理器的普及,并发编程变得越来越重要。Java提供了丰富的并发编程工具,如synchronized关键字、显式锁Lock、原子类、并发容器等。本文将深入探讨Java并发编程的核心概念,包括线程安全、死锁、资源竞争等,并分享一些性能优化的技巧。