LockSupport的park与unpark调用顺序验证

本文通过三个实验深入探讨了Java中LockSupport类的unpark和park方法的使用时机与效果,揭示了线程唤醒的机制。实验表明,unpark必须在线程启动后才能生效,无论是在park前还是后调用,都能成功唤醒线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.unpark在thread start之前调用

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1 start");

                try{
                    Thread.sleep(2000);
                } catch (Exception e){
                    e.printStackTrace();
                }

                System.out.println("thread1 run before park");

                LockSupport.park();

                System.out.println("thread1 run after park");
            }
        });
        LockSupport.unpark(thread1);
        thread1.start();

        System.out.println("main thread");

    }

执行结果:

main thread
thread1 start
thread1 run before park

(程序没有退出,线程1一直挂起)

2.unpark在park之前执行

public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1 start");

                try{
                    Thread.sleep(2000);
                } catch (Exception e){
                    e.printStackTrace();
                }

                System.out.println("thread1 run before park");

                LockSupport.park();

                System.out.println("thread1 run after park");
            }
        });
        thread1.start();
        LockSupport.unpark(thread1);

        System.out.println("main thread");

    }

执行结果:

thread1 start
main thread
thread1 run before park
thread1 run after park

3.unpark在park之后执行

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1 run before park");

                LockSupport.park();

                System.out.println("thread1 run after park");
            }
        });

        thread1.start();

        System.out.println("main thread");
        Thread.sleep(3000);
        LockSupport.unpark(thread1);
    }

执行结果:

main thread
thread1 run before park
thread1 run after park

总结

1.unpark必须在thread start之后才有用,之前调用没有任何效果;

2.thread start之后,unpark在park之前还是之后,作用是一样的,都会重新唤醒线程;

<think>我们正在讨论Java中的加锁机制,除了synchronized关键字之外,还有其他多种方式。以下将详细介绍几种常见的加锁机制: 1. ReentrantLock(可重入锁) 2. ReadWriteLock(读写锁) 3. StampedLock(邮戳锁) 4. 使用并发工具类(如Semaphore、CountDownLatch等) 5. 原子变量(Atomic Classes) 6. LockSupport(底层锁支持) 下面逐一展开: ### 1. ReentrantLock(可重入锁) `ReentrantLock`是`java.util.concurrent.locks`包下的一个类,它提供了`synchronized`相同的互斥性和可重入性,但增加了额外的功能,如可中断的锁获取、超时获取锁、公平锁等[^1][^2]。 **基本用法**: ```java Lock lock = new ReentrantLock(); lock.lock(); // 获取锁 try { // 临界区代码 } finally { lock.unlock(); // 必须在finally块中释放锁 } ``` **特点**: - **可中断**:`lockInterruptibly()`方法允许在等待锁的过程中响应中断。 - **超时获取**:`tryLock(long time, TimeUnit unit)`尝试在指定时间内获取锁。 - **公平锁**:构造函数传入`true`可以创建公平锁(按等待顺序分配锁),但可能降低吞吐量。 **示例**: ```java ReentrantLock lock = new ReentrantLock(true); // 公平锁 if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试1秒内获取锁 try { // 操作 } finally { lock.unlock(); } } ``` ### 2. ReadWriteLock(读写锁) `ReentrantReadWriteLock`实现了`ReadWriteLock`接口,将锁分为读锁和写锁,适用于读多写少的场景[^2]。 **特点**: - **读锁(共享锁)**:多个线程可以同时持有读锁。 - **写锁(排他锁)**:同一时刻只有一个线程可以持有写锁,且写锁获取时不能有读锁或其他写锁。 **用法**: ```java ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); // 读操作 readLock.lock(); try { // 读取数据 } finally { readLock.unlock(); } // 写操作 writeLock.lock(); try { // 写入数据 } finally { writeLock.unlock(); } ``` ### 3. StampedLock(邮戳锁) Java 8引入的`StampedLock`是对读写锁的改进,支持乐观读模式,性能通常优于`ReentrantReadWriteLock`[^3]。 **三种模式**: - **写锁(Writing)**:独占锁,类似写锁。 - **悲观读锁(Reading)**:共享锁,类似读锁。 - **乐观读(Optimistic Reading)**:无锁机制,通过`tryOptimisticRead()`获取一个邮戳(stamp),然后验证该邮戳是否仍然有效(即在此期间是否有写操作)。 **示例**: ```java StampedLock lock = new StampedLock(); // 写操作 long stamp = lock.writeLock(); try { // 写入数据 } finally { lock.unlockWrite(stamp); } // 乐观读 long stamp = lock.tryOptimisticRead(); // 读取数据(此时可能被写操作修改) if (!lock.validate(stamp)) { // 检查期间是否有写操作 stamp = lock.readLock(); // 升级为悲观读锁 try { // 重新读取数据 } finally { lock.unlockRead(stamp); } } ``` ### 4. 并发工具类 Java并发包(`java.util.concurrent`)提供了多种同步工具: - **Semaphore(信号量)**:控制同时访问特定资源的线程数量[^4]。 ```java Semaphore semaphore = new Semaphore(5); // 允许5个线程同时访问 semaphore.acquire(); // 获取许可 try { // 使用资源 } finally { semaphore.release(); } ``` - **CountDownLatch(倒计时门闩)**:等待多个线程完成操作后再继续[^4]。 ```java CountDownLatch latch = new CountDownLatch(3); // 多个线程执行任务,每个完成后调用latch.countDown() latch.await(); // 阻塞直到计数器为0 ``` - **CyclicBarrier(循环屏障)**:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障才继续执行[^4]。 ```java CyclicBarrier barrier = new CyclicBarrier(3, ()->System.out.println("所有线程到达屏障")); // 每个线程调用barrier.await()等待 ``` ### 5. 原子变量(Atomic Classes) `java.util.concurrent.atomic`包提供了一系列原子类(如`AtomicInteger`、`AtomicReference`等),通过CAS(Compare-And-Swap)操作实现无锁线程安全[^5]。 **示例**: ```java AtomicInteger count = new AtomicInteger(0); count.incrementAndGet(); // 原子自增 ``` **优点**:避免锁开销,适用于高并发场景。 ### 6. LockSupport(底层锁支持) `LockSupport`提供基本的线程阻塞原语,用于创建更高级的锁和同步工具[^6]。 **核心方法**: - `park()`:阻塞当前线程。 - `unpark(Thread thread)`:唤醒指定线程。 **示例**: ```java Thread t = new Thread(() -> { LockSupport.park(); // 阻塞 // 被唤醒后继续执行 }); t.start(); LockSupport.unpark(t); // 唤醒线程t ``` --- ### 总结对比 | **锁机制** | **特点** | **适用场景** | |---------------------|-------------------------------------------------------------------------|----------------------------------| | `synchronized` | 内置关键字,自动获取/释放锁,非公平锁 | 简单同步场景 | | `ReentrantLock` | 可重入、可中断、超时、公平锁,需手动释放 | 需要高级功能的同步 | | `ReadWriteLock` | 读写分离,提高读性能 | 读多写少场景 | | `StampedLock` | 支持乐观读,性能更高 | 读远多于写的场景 | | `Semaphore` | 控制并发线程数 | 资源池、限流 | | `CountDownLatch` | 等待多个线程完成 | 并行任务同步 | | `CyclicBarrier` | 多个线程相互等待至屏障点 | 分阶段任务 | | 原子变量 | 无锁,CAS操作 | 计数器等简单原子操作 | | `LockSupport` | 底层线程阻塞/唤醒 | 构建自定义同步工具 | §§相关问题§§ 1. ReentrantLock的公平锁和非公平锁在性能上有何差异? 2. 如何避免使用StampedLock时出现死锁? 3. CAS操作在并发编程中存在哪些问题? 4. Semaphore和CountDownLatch的主要区别是什么? 5. 原子类锁机制相比有哪些优势和劣势? [^1]: ReentrantLock提供了可中断、超时和公平锁功能 [^2]: ReadWriteLock实现了读写分离,提高读性能 [^3]: StampedLock的乐观读模式在高并发读场景下性能更优 [^4]: Semaphore、CountDownLatch和CyclicBarrier是常用的同步工具 [^5]: 原子类通过CAS实现无锁线程安全 [^6]: LockSupport提供线程阻塞和唤醒的底层原语</think>### Java 加锁机制详解(除 synchronized 外) 以下是 Java 中除 `synchronized` 关键字外的其他主要加锁方式,每种机制都有其独特的适用场景和特性: --- #### 1. **ReentrantLock(可重入锁)** 最常用的显式锁实现,提供比 `synchronized` 更灵活的控制: ```java Lock lock = new ReentrantLock(); // 创建锁对象 lock.lock(); // 手动加锁 try { // 临界区代码 } finally { lock.unlock(); // 必须在finally中释放锁[^1] } ``` **核心特性**: - **可中断锁**:`lockInterruptibly()` 支持中断等待 - **尝试获取锁**:`tryLock()` 立即返回成功状态 - **公平性选择**:构造函数指定公平/非公平锁 - **锁绑定条件**:通过 `newCondition()` 实现精细线程协调 --- #### 2. **ReadWriteLock(读写锁)** 适用于读多写少场景,提升并发性能: ```java ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); // 共享读锁 Lock writeLock = rwLock.writeLock(); // 排他写锁 // 读操作 readLock.lock(); try { /* 并发读取 */ } finally { readLock.unlock(); } // 写操作 writeLock.lock(); try { /* 独占写入 */ } finally { writeLock.unlock(); } ``` **优势**:允许多线程同时读,写操作独占锁[^2] --- #### 3. **StampedLock(邮戳锁)** Java 8 引入的优化锁,支持三种访问模式: ```java StampedLock lock = new StampedLock(); // 乐观读(无锁) long stamp = lock.tryOptimisticRead(); if (!lock.validate(stamp)) { // 验证是否被修改 stamp = lock.readLock(); // 升级为悲观读 try { /* 重新读取 */ } finally { lock.unlockRead(stamp); } } // 写锁 long stamp = lock.writeLock(); try { /* 写入操作 */ } finally { lock.unlockWrite(stamp); } ``` **特点**:乐观读避免锁竞争,适合读远多于写的场景[^3] --- #### 4. **原子变量(Atomic Classes)** 基于 CAS 的无锁并发实现: ```java AtomicInteger counter = new AtomicInteger(0); // 原子操作 counter.incrementAndGet(); // 原子自增 counter.compareAndSet(5, 10); // CAS更新 ``` **适用场景**:计数器、状态标志等简单操作,性能优于锁[^4] --- #### 5. **LockSupport** 底层线程阻塞工具: ```java // 阻塞当前线程 LockSupport.park(); // 唤醒指定线程 LockSupport.unpark(targetThread); ``` **用途**:构建高级同步工具(如 AQS 实现) --- #### 6. **信号量(Semaphore)** 控制资源访问的并发数: ```java Semaphore sem = new Semaphore(5); // 允许5个线程并发 sem.acquire(); // 获取许可 try { /* 使用资源 */ } finally { sem.release(); } // 释放许可 ``` **典型场景**:数据库连接池、限流控制[^5] --- #### 7. **同步工具类** - **CountDownLatch**:等待多个线程完成 ```java CountDownLatch latch = new CountDownLatch(3); // 工作线程中 latch.countDown(); // 主线程中 latch.await(); // 阻塞直到计数器归零 ``` - **CyclicBarrier**:多线程到达屏障后统一执行 - **Phaser**:分阶段的多线程任务控制 --- ### 锁机制对比表 | **机制** | **特点** | **适用场景** | |-------------------|----------------------------------|---------------------------| | `ReentrantLock` | 可中断、超时、公平锁 | 需要精细控制的同步 | | `ReadWriteLock` | 读写分离 | 读多写少的数据结构 | | `StampedLock` | 乐观读、低开销 | 高并发读场景 | | 原子变量 | 无锁、CAS 操作 | 计数器等简单状态更新 | | `Semaphore` | 控制并发数量 | 资源池管理 | | `CountDownLatch` | 一次性等待多线程完成 | 并行任务初始化 | | `CyclicBarrier` | 可重复使用的线程屏障 | 分阶段并行计算 | --- ### 选择建议 1. 优考虑**原子变量**实现无锁编程 2. 读写冲突时用 **ReadWriteLock/StampedLock** 3. 需要超时/中断控制用 **ReentrantLock** 4. 资源池管理用 **Semaphore** 5. 复杂协作使用 **CountDownLatch/CyclicBarrier**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值