LockSupport 详解

LockSupport 详解

LockSupport 是 Java 中提供的一组用于线程同步和线程控制的工具类。它属于 java.util.concurrent 包,旨在==提供一种更加高效且低级的线程挂起和恢复的机制==。LockSupport 通过实现“阻塞 - 唤醒”机制来实现线程的挂起与恢复,常用于实现自定义的并发控制器(如锁(Lock)、信号量(Semaphore)、条件变量(Condition)等)。
LockSupport 本身提供了比传统的 wait()/notify() 更加灵活和高效的方式来控制线程挂起与唤醒,避免了 Object 的监视器锁(monitor)机制的性能瓶颋。
当我们想自己灵活控制线程的挂起和继续执行的时候,我们也可以使用 LockSupport 来实现,非常方便。

LockSupport 的主要方法

LockSupport.park()

park() 方法使当前线程挂起,直到被唤醒。线程会进入等待状态,并且被阻塞,直到满足以下条件之一:

public static void park();

功能:

LockSupport.parkNanos(long nanos)

parkNanos(long nanos) 方法使当前线程挂起指定时间(纳秒为单位)。如果线程没有被唤醒且时间到期,它将自动恢复执行。可以有效防止无人唤醒造成死锁

public static void parkNanos(long nanos);

我们可以通过 TimeUnit 的 toNanos 方法,轻松将其他单位的时间转化为纳秒,比如 TimeUnit.SECONDS.toNanos(2)
功能:

LockSupport.parkUntil(long deadline)

parkUntil(long deadline) 方法使当前线程挂起,直到指定的时间戳(绝对时间)。这个方法会挂起线程直到 deadline 到期为止。用的少,一般都是用 LockSupport.parkNanos(long nanos) 指定挂起的相对时间。

public static void parkUntil(long deadline);

功能:

LockSupport.unpark(Thread thread)

unpark(Thread thread) 方法唤醒指定的线程。被唤醒的线程将会继续执行,前提是该线程之前调用了 park() 并挂起了。如果该线程之前没有被挂起,它会直接返回。

public static void unpark(Thread thread);

功能:

LockSupport 的工作原理

LockSupport 类实现线程挂起和唤醒的方式其实就是调用 Unsafeparkunpark 方法,而 Unsafeparkunpark 方法底层依赖于操作系统的的指令(如 futexmutex 等)。当线程调用 park() 时,它会被挂起并进入内核的阻塞队列。线程将会保持挂起状态,直到满足以下条件之一:

挂起与唤醒的机制

使用场景

LockSupport 主要用于需要低级控制线程挂起与恢复的场景,它比传统的 Object.wait()Object.notify() 更为灵活,特别是在高并发和性能敏感的应用中。
一些常见的使用场景包括:

实现线程池

LockSupport 常用于实现线程池的工作机制,尤其是在等待任务执行和空闲线程之间的调度。

// 示例:通过 LockSupport 实现线程的挂起与唤醒
Thread workerThread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        LockSupport.park();
        // 执行任务...
    }
});
workerThread.start();

// 需要唤醒工作线程
LockSupport.unpark(workerThread);

自定义同步器

开发自定义的锁、信号量、条件变量等同步器时,LockSupport 提供了更低级和灵活的线程控制能力。例如,基于 LockSupport 可以构建一个类似 CountDownLatchCyclicBarrier 的同步工具。

优化性能

由于 LockSupport 不依赖传统的基于对象监视器(monitor)的同步机制(即不使用 synchronizedwait/notify),它避免了在高并发情况下的性能瓶颈。

非阻塞等待

LockSupport.parkNanos() 提供了一种非阻塞的等待机制,允许线程在等待期间进行一定时间的自旋,这对于实现高效的并发控制尤为重要。

wait()/notify() 的比较

Object.wait()Object.notify() 不同,LockSupport 的方法无需依赖于监视器锁。wait()notify() 需要在 synchronized 代码块内调用,并且会持有对象锁,可能导致死锁和性能问题。而 LockSupport 是基于更底层的操作系统同步指令(如 futex),避免了对象锁的开销。

特性 LockSupport Object.wait() / notify()
依赖于监视器锁 是(synchronized
能否中断线程 可以(通过中断) 可以(通过 Thread.interrupted()
唤醒方式 通过 unpark() 通过 notify()notifyAll()
性能 更高效,依赖于操作系统的指令 可能有较大性能开销(上下文切换)
使用场景 高效的线程挂起、唤醒与线程池实现 一般的线程同步和通信

示例代码

同时执行三个线程,然后在这三个线程内部将自己挂起,然后在主线程(main)中将这三个线程唤醒,三个县城继续往后执行

public static void main(String[] args) {  
    List<Thread> threads = new ArrayList<>();  
    int rounds = 10;  
    for (int i = 0; i < 3; i++) {  
        int finalI = i;  
        Thread thread = new Thread(() -> {  
            int nowCount = 0;  
            String threadName = "Thread " + finalI;  
            System.out.println(LocalDateTime.now() + "-" + threadName + " started");  
            while (nowCount < rounds) {  
                if (nowCount == 3) {  
                    // 需要线程自己调用方法主动挂起自己  
                    LockSupport.park();  
                    System.out.println(LocalDateTime.now() + "-" + threadName + " 线程恢复");  
                }  
                try {  
                    Thread.sleep(200);  
                } catch (InterruptedException e) {  
                    // throw new RuntimeException(e);  
                }  
                System.out.println(LocalDateTime.now() + "-" + threadName + " now Round is:" + nowCount);  
                nowCount++;  
            }  
        });  
        threads.add(thread);  
        thread.start();  
    }  
    try {  
        Thread.sleep(3000);  
    } catch (InterruptedException e) {  
        // throw new RuntimeException(e);  
    }  
    System.out.println("重新启动所有线程");  
    for (Thread thread : threads) {  
        // 从另一个线程来调用方法来唤醒那些自己挂起自己的线程  
        // 你自己挂起你自己 + 等待别人来唤醒你 这样的设计限制了挂起线程的操作的发起方,即自己线程自己才可以挂起自己,别人不行,其实简化了多线程编程的心智负担,  
        // 因为如果别的线程可以挂起别的线程,A 挂起了 B,同时 B 也挂起了 A,好家伙,相互挂起,死锁将更加普遍,  
        // 限制只有自己才能挂起自己,产生死锁的原因就变成了没有人来唤醒自己,  
        LockSupport.unpark(thread);  
    }  
}

执行输出

2025-01-19T15:58:28.867800300-Thread 1 started
2025-01-19T15:58:28.867800300-Thread 2 started
2025-01-19T15:58:28.867800300-Thread 0 started
2025-01-19T15:58:29.081427500-Thread 0 now Round is:0
2025-01-19T15:58:29.081427500-Thread 1 now Round is:0
2025-01-19T15:58:29.081427500-Thread 2 now Round is:0
2025-01-19T15:58:29.303496800-Thread 2 now Round is:1
2025-01-19T15:58:29.303496800-Thread 1 now Round is:1
2025-01-19T15:58:29.303496800-Thread 0 now Round is:1
2025-01-19T15:58:29.509052200-Thread 2 now Round is:2
2025-01-19T15:58:29.509052200-Thread 0 now Round is:2
2025-01-19T15:58:29.509052200-Thread 1 now Round is:2
重新启动所有线程
2025-01-19T15:58:31.866113900-Thread 1 线程恢复
2025-01-19T15:58:31.866113900-Thread 2 线程恢复
2025-01-19T15:58:31.866113900-Thread 0 线程恢复
2025-01-19T15:58:32.073400200-Thread 1 now Round is:3
2025-01-19T15:58:32.073400200-Thread 0 now Round is:3
2025-01-19T15:58:32.073400200-Thread 2 now Round is:3
2025-01-19T15:58:32.277323700-Thread 2 now Round is:4
2025-01-19T15:58:32.277323700-Thread 0 now Round is:4
2025-01-19T15:58:32.277323700-Thread 1 now Round is:4
2025-01-19T15:58:32.482627900-Thread 1 now Round is:5
2025-01-19T15:58:32.483627100-Thread 0 now Round is:5
2025-01-19T15:58:32.483627100-Thread 2 now Round is:5
2025-01-19T15:58:32.685546400-Thread 1 now Round is:6
2025-01-19T15:58:32.685546400-Thread 0 now Round is:6
2025-01-19T15:58:32.686548100-Thread 2 now Round is:6
2025-01-19T15:58:32.890007700-Thread 1 now Round is:7
2025-01-19T15:58:32.890007700-Thread 0 now Round is:7
2025-01-19T15:58:32.890007700-Thread 2 now Round is:7
2025-01-19T15:58:33.096232200-Thread 2 now Round is:8
2025-01-19T15:58:33.096232200-Thread 0 now Round is:8
2025-01-19T15:58:33.096232200-Thread 1 now Round is:8
2025-01-19T15:58:33.300468100-Thread 1 now Round is:9
2025-01-19T15:58:33.300468100-Thread 0 now Round is:9
2025-01-19T15:58:33.300468100-Thread 2 now Round is:9

这里我们多说两句 Java 中的多线程 API 设计范式,其为:线程自己挂起自己 + 等待别人来唤醒它

public static void main(String[] args) {  
    List<Thread> threads = new ArrayList<>();  
    int rounds = 10;  
    for (int i = 0; i < 1; i++) {  
        int finalI = i;  
        Thread thread = new Thread(() -> {  
            int nowCount = 0;  
            String threadName = "Thread " + finalI;  
            System.out.println(LocalDateTime.now() + "-" + threadName + " started");  
            while (nowCount < rounds) {  
                if (nowCount == 3) {  
                    // 需要线程自己调用方法主动挂起自己  
                    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));  
                    System.out.println(LocalDateTime.now() + "-" + threadName + " 线程恢复");  
                }  
                try {  
                    Thread.sleep(200);  
                } catch (InterruptedException e) {  
                    // throw new RuntimeException(e);  
                }  
                System.out.println(LocalDateTime.now() + "-" + threadName + " now Round is:" + nowCount);  
                nowCount++;  
            }  
        });  
        threads.add(thread);  
        thread.start();  
    }  
}

执行输出

2025-01-19T16:07:11.445919900-Thread 0 started
2025-01-19T16:07:11.669957100-Thread 0 now Round is:0
2025-01-19T16:07:11.886799300-Thread 0 now Round is:1
2025-01-19T16:07:12.092065-Thread 0 now Round is:2
2025-01-19T16:07:14.099796300-Thread 0 线程恢复
2025-01-19T16:07:14.318365200-Thread 0 now Round is:3
2025-01-19T16:07:14.523887600-Thread 0 now Round is:4
2025-01-19T16:07:14.728722300-Thread 0 now Round is:5
2025-01-19T16:07:14.933144-Thread 0 now Round is:6
2025-01-19T16:07:15.139189-Thread 0 now Round is:7
2025-01-19T16:07:15.342590700-Thread 0 now Round is:8
2025-01-19T16:07:15.546135100-Thread 0 now Round is:9

其实 ObjectCondition 也有类似的方法,比如 Objectwait(long timeout)Conditionawait(long time, TimeUnit unit) 方法。

总结

LockSupport 是一个灵活、轻量级的线程挂起和恢复工具,它不依赖于 synchronizedwait/notify,可以在不占用监视器锁的情况下高效地进行线程管理。它适用于那些需要精细控制线程状态的场景,特别是在高并发的并发程序设计中。