park和unpark机制详解

LockSupport的park/unpark是Java并发中另一种重要的线程阻塞唤醒机制,相比wait/notify更加灵活强大。

什么是LockSupport

基本概念

LockSupport:是用来创建锁和其他同步类的线程原语(Thread Primitive)。

位置java.util.concurrent.locks.LockSupport

作用

  • 🔧 提供更灵活的线程阻塞和唤醒机制
  • 🔧 是构建其他并发工具的基础(如AQS)
  • 🔧 比wait/notify更强大和易用

核心方法

public class LockSupport {
    // 阻塞当前线程
    public static void park()
    
    // 阻塞当前线程,最多等待nanos纳秒
    public static void parkNanos(long nanos)
    
    // 阻塞当前线程,直到deadline时间点
    public static void parkUntil(long deadline)
    
    // 阻塞当前线程,带blocker对象(用于诊断)
    public static void park(Object blocker)
    public static void parkNanos(Object blocker, long nanos)
    public static void parkUntil(Object blocker, long deadline)
    
    // 唤醒指定线程
    public static void unpark(Thread thread)
}

park/unpark基本使用

示例1:基本用法

import java.util.concurrent.locks.LockSupport;

public class ParkUnparkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("start..."); // 1
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("park..."); // 2
            LockSupport.park(); // 阻塞
            System.out.println("resume..."); // 4
        }, "t1");
        
        t1.start();
        
        Thread.sleep(2000); // 确保t1先park
        System.out.println("unpark..."); // 3
        LockSupport.unpark(t1); // 唤醒t1
    }
}

输出

start...
park...
unpark...
resume...

执行流程

T1: t1启动,输出"start..."
T2: t1 sleep 1秒
T3: t1输出"park...",调用park()阻塞
T4: 主线程 sleep 2秒后,输出"unpark..."
T5: 主线程调用unpark(t1),唤醒t1
T6: t1恢复执行,输出"resume..."

示例2:可以先unpark

重要特性:park/unpark可以先unpark再park,效果一样!

public class UnparkFirstDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("start...");
            try {
                Thread.sleep(2000); // sleep 2秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("park...");
            LockSupport.park(); // 不会阻塞,立即继续
            System.out.println("resume...");
        }, "t1");
        
        t1.start();
        
        Thread.sleep(1000); // sleep 1秒
        System.out.println("unpark...");
        LockSupport.unpark(t1); // 先unpark(t1还在sleep中)
    }
}

输出

start...
unpark...        ← 先unpark
park...          ← 后park,但不阻塞!
resume...        ← 立即继续执行

执行流程

T1: t1启动,输出"start...",开始sleep 2秒
T2: 主线程sleep 1秒后,输出"unpark..."
T3: 主线程调用unpark(t1),设置许可为1
T4: t1 sleep结束,输出"park..."
T5: t1调用park(),发现有许可,消耗许可,不阻塞
T6: t1输出"resume..."

对比wait/notify

// ❌ wait/notify不能先notify
synchronized (lock) {
    lock.notify(); // 先notify
}
synchronized (lock) {
    lock.wait(); // 后wait,会一直等待!
}

// ✅ park/unpark可以先unpark
LockSupport.unpark(t1); // 先unpark
LockSupport.park(); // 后park,不会阻塞

示例3:多次unpark只有一次效果

public class MultipleUnparkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("start...");
            
            LockSupport.park(); // 第一次park
            System.out.println("第一次被唤醒");
            
            LockSupport.park(); // 第二次park
            System.out.println("第二次被唤醒");
            
            LockSupport.park(); // 第三次park
            System.out.println("第三次被唤醒");
        }, "t1");
        
        t1.start();
        Thread.sleep(1000);
        
        // 连续unpark 3次
        System.out.println("第一次unpark");
        LockSupport.unpark(t1);
        System.out.println("第二次unpark");
        LockSupport.unpark(t1);
        System.out.println("第三次unpark");
        LockSupport.unpark(t1);
    }
}

输出

start...
第一次unpark
第二次unpark
第三次unpark
第一次被唤醒
(程序卡住,t1在第二次park处阻塞)

原因

  • park/unpark使用**许可(permit)**机制
  • 许可最多只有1个,多次unpark不会累加
  • 第一次park消耗了唯一的许可,后面的park会阻塞
初始状态:许可 = 0

unpark():  许可 = 1
unpark():  许可 = 1  ← 不会变成2!
unpark():  许可 = 1  ← 不会变成3!

park():    许可 = 0  ← 消耗许可
park():    阻塞!    ← 没有许可了

示例4:带blocker参数

blocker参数用于诊断和监控,可以通过jstack看到阻塞原因:

public class BlockerDemo {
    public static void main(String[] args) throws InterruptedException {
        Object blocker = new Object();
        
        Thread t1 = new Thread(() -> {
            System.out.println("park with blocker");
            LockSupport.park(blocker); // 带blocker
            System.out.println("unparked");
        }, "t1");
        
        t1.start();
        Thread.sleep(1000);
        
        // 使用jstack查看:
        // "t1" #12 prio=5 os_prio=0 waiting on condition
        //   parking to wait for <0x000000076b5bf630> (a java.lang.Object)
    }
}

park/unpark vs wait/notify

对比表格

特性 park/unpark wait/notify
调用条件 无需synchronized 必须在synchronized块中
唤醒线程 精确唤醒指定线程 notify随机唤醒一个
顺序要求 可以先unpark 不能先notify
许可机制 有许可(permit)概念
释放锁 不会释放锁 wait会释放锁
所属类 LockSupport(工具类) Object(任何对象)
使用难度 简单 较复杂

详细对比

1. 调用条件
// wait/notify:必须在synchronized块中
synchronized (lock) {
    lock.wait();
}
synchronized (lock) {
    lock.notify();
}

// park/unpark:无需synchronized
LockSupport.park();
LockSupport.unpark(thread);
2. 唤醒方式
// wait/notify:随机唤醒或全部唤醒
Object lock = new Object();
synchronized (lock) {
    lock.notify();    // 随机唤醒一个
    lock.notifyAll(); // 唤醒所有
}

// park/unpark:精确唤醒指定线程
LockSupport.unpark(t1); // 精确唤醒t1
LockSupport.unpark(t2); // 精确唤醒t2
3. 顺序要求
// wait/notify:不能先notify
synchronized (lock) {
    lock.notify(); // 先notify无效
}
synchronized (lock) {
    lock.wait(); // 后wait会一直等待
}

// park/unpark:可以先unpark
LockSupport.unpark(t1); // 先unpark有效
LockSupport.park(); // 后park不阻塞
4. 是否释放锁
// wait:释放锁
synchronized (lock) {
    lock.wait(); // 释放lock,其他线程可以获取
}

// park:不释放锁
synchronized (lock) {
    LockSupport.park(); // 不释放lock,其他线程无法获取
}

演示park不释放锁

public class ParkNotReleaseLock {
    private static final Object lock = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t1: 获得锁");
                System.out.println("t1: 开始park");
                LockSupport.park(); // park不释放锁
                System.out.println("t1: park结束");
            }
        }, "t1");
        
        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("t2: 获得锁"); // 永远不会执行
            }
        }, "t2");
        
        t1.start();
        Thread.sleep(100);
        t2.start();
        Thread.sleep(100);
        
        System.out.println("主线程: unpark t1");
        LockSupport.unpark(t1); // 唤醒t1
    }
}

输出

t1: 获得锁
t1: 开始park
主线程: unpark t1
t1: park结束
t2: 获得锁     ← t1释放锁后,t2才能执行

park/unpark原理

许可(Permit)机制

park/unpark的核心是许可(permit)

许可的特点:
1. 许可只有0或1,不会累加
2. park()消耗许可(1 → 0)
3. unpark()增加许可(0 → 1,1 → 1不变)
4. 有许可时park()不阻塞
5. 无许可时park()阻塞

底层实现(_counter)

每个线程都有一个与LockSupport关联的许可(permit),在HotSpot中通过_counter变量实现:

// HotSpot源码(简化版)
class Parker : public os::PlatformParker {
private:
    volatile int _counter;  // 许可计数器(0或1)
    pthread_mutex_t _mutex; // 互斥锁
    pthread_cond_t _cond;   // 条件变量
    
public:
    void park() {
        // 如果_counter > 0,消耗许可,不阻塞
        if (_counter > 0) {
            _counter = 0;
            return;
        }
        
        // _counter == 0,阻塞等待
        pthread_mutex_lock(&_mutex);
        while (_counter == 0) {
            pthread_cond_wait(&_cond, &_mutex);
        }
        _counter = 0;
        pthread_mutex_unlock(&_mutex);
    }
    
    void unpark() {
        pthread_mutex_lock(&_mutex);
        _counter = 1; // 设置许可为1
        pthread_cond_signal(&_cond); // 唤醒等待线程
        pthread_mutex_unlock(&_mutex);
    }
};

先park的情况

初始状态:_counter = 0

1. Thread调用park()
   ├─ 检查_counter == 0
   ├─ 获取_mutex互斥锁
   ├─ 进入_cond条件变量等待
   └─ 线程阻塞

2. 调用unpark(Thread)
   ├─ 获取_mutex互斥锁
   ├─ 设置_counter = 1
   ├─ 唤醒_cond上等待的线程
   └─ 释放_mutex

3. Thread被唤醒
   ├─ 从_cond等待中返回
   ├─ 设置_counter = 0
   ├─ 释放_mutex
   └─ 继续执行

图示

Thread                          Parker对象
  │                              _counter = 0
  │                              _mutex
  │                              _cond
  │
  ├─ park()
  │   ├─ 检查_counter == 0
  │   ├─ 获取_mutex
  │   ├─ 进入_cond等待
  │   │    ⏸️ 阻塞中...
  │   │
  │   │                          另一个线程调用unpark()
  │   │                          ├─ 获取_mutex
  │   │                          ├─ _counter = 1
  │   │                          ├─ signal(_cond)
  │   │                          └─ 释放_mutex
  │   │
  │   ├─ 被唤醒 ✅
  │   ├─ _counter = 0
  │   └─ 释放_mutex
  │
  └─ 继续执行

先unpark的情况

初始状态:_counter = 0

1. 调用unpark(Thread)
   ├─ 获取_mutex互斥锁
   ├─ 设置_counter = 1
   ├─ signal(_cond)(没有线程等待,无效果)
   └─ 释放_mutex

2. Thread调用park()
   ├─ 检查_counter == 1  ← 有许可!
   ├─ 设置_counter = 0
   └─ 直接返回,不阻塞  ✅

图示

Thread                          Parker对象
  │                              _counter = 0
  │
  │                          另一个线程先调用unpark()
  │                          ├─ 获取_mutex
  │                          ├─ _counter = 1  ← 设置许可
  │                          └─ 释放_mutex
  │
  ├─ park()
  │   ├─ 检查_counter == 1  ← 有许可!
  │   ├─ _counter = 0       ← 消耗许可
  │   └─ 直接返回 ✅
  │
  └─ 继续执行

多次unpark的情况

初始状态:_counter = 0

1. 第一次unpark()
   └─ _counter = 1

2. 第二次unpark()
   └─ _counter = 1  ← 不会变成2!

3. 第三次unpark()
   └─ _counter = 1  ← 还是1

4. 第一次park()
   └─ _counter = 0  ← 消耗许可

5. 第二次park()
   └─ _counter == 0,阻塞!

实战应用

应用1:自定义锁

public class SimpleLock {
    private volatile boolean locked = false;
    private Thread owner;
    
    public void lock() {
        Thread current = Thread.currentThread();
        
        while (true) {
            // 尝试获取锁
            if (!locked) {
                synchronized (this) {
                    if (!locked) {
                        locked = true;
                        owner = current;
                        return;
                    }
                }
            }
            
            // 获取失败,park等待
            LockSupport.park();
        }
    }
    
    public void unlock() {
        synchronized (this) {
            if (owner == Thread.currentThread()) {
                locked = false;
                owner = null;
                
                // 唤醒所有等待的线程(实际应该维护等待队列)
                // 这里简化处理
            }
        }
    }
}

应用2:一次性屏障

public class OneShotLatch {
    private volatile boolean opened = false;
    private List<Thread> waiters = new ArrayList<>();
    
    public void await() {
        if (opened) {
            return; // 已经打开,直接返回
        }
        
        Thread current = Thread.currentThread();
        synchronized (this) {
            if (opened) {
                return;
            }
            waiters.add(current);
        }
        
        // park等待
        LockSupport.park();
    }
    
    public void open() {
        synchronized (this) {
            if (opened) {
                return;
            }
            opened = true;
            
            // 唤醒所有等待的线程
            for (Thread waiter : waiters) {
                LockSupport.unpark(waiter);
            }
            waiters.clear();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        OneShotLatch latch = new OneShotLatch();
        
        // 创建5个线程等待
        for (int i = 1; i <= 5; i++) {
            int id = i;
            new Thread(() -> {
                System.out.println("线程" + id + " 等待...");
                latch.await(); // 阻塞等待
                System.out.println("线程" + id + " 通过!");
            }).start();
        }
        
        Thread.sleep(2000);
        System.out.println("\n主线程打开门栓");
        latch.open(); // 打开,所有线程通过
    }
}

输出

线程1 等待...
线程2 等待...
线程3 等待...
线程4 等待...
线程5 等待...

主线程打开门栓
线程1 通过!
线程2 通过!
线程3 通过!
线程4 通过!
线程5 通过!

应用3:interrupt()中断park

park可以被interrupt()中断:

public class InterruptParkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("t1: park...");
            LockSupport.park();
            System.out.println("t1: 被中断唤醒");
            System.out.println("t1: 中断状态 = " + 
                             Thread.currentThread().isInterrupted());
        }, "t1");
        
        t1.start();
        Thread.sleep(1000);
        
        System.out.println("主线程: interrupt t1");
        t1.interrupt(); // 中断t1
    }
}

输出

t1: park...
主线程: interrupt t1
t1: 被中断唤醒
t1: 中断状态 = true  ← 中断标记不会被清除

注意

  • interrupt()可以唤醒park的线程
  • 中断标记不会被清除(与wait不同)
  • 如果中断标记为true,park会失效
// 演示:中断标记为true时,park失效
Thread t1 = new Thread(() -> {
    System.out.println("park 1");
    LockSupport.park();
    System.out.println("unpark 1");
    
    // 此时中断标记为true
    System.out.println("中断状态: " + Thread.currentThread().isInterrupted());
    
    System.out.println("park 2");
    LockSupport.park(); // ❌ 失效!不会阻塞
    System.out.println("unpark 2"); // 立即执行
});

t1.start();
Thread.sleep(1000);
t1.interrupt();

解决方法:使用Thread.interrupted()清除标记

System.out.println("park 1");
LockSupport.park();
System.out.println("unpark 1");

// 清除中断标记
Thread.interrupted();

System.out.println("park 2");
LockSupport.park(); // ✅ 现在可以正常park
System.out.println("unpark 2");

🎯 知识点总结

park/unpark核心特性

特性 说明
无需synchronized 可以在任何地方调用
精确唤醒 unpark(thread)精确唤醒指定线程
可以先unpark 先unpark再park不会阻塞
许可机制 许可只有0或1,不会累加
不释放锁 park不会释放synchronized锁
可被中断 interrupt()可以唤醒park

底层原理

每个线程关联一个Parker对象:
├─ _counter:许可计数器(0或1)
├─ _mutex:互斥锁
└─ _cond:条件变量

park():
├─ 如果_counter > 0:消耗许可,不阻塞
└─ 如果_counter == 0:进入_cond等待,阻塞

unpark():
├─ 设置_counter = 1
└─ 唤醒_cond上的线程

使用建议

  1. 需要精确唤醒时使用park/unpark
  2. 不想写synchronized时使用park/unpark
  3. 构建自定义同步工具时使用
  4. ⚠️ 注意中断标记会影响park
  5. ⚠️ park不会释放synchronized锁

💡 常见面试题

Q1:park/unpark和wait/notify有什么区别?

  1. park/unpark不需要synchronized,wait/notify必须在synchronized块中
  2. park/unpark可以精确唤醒指定线程,notify随机唤醒
  3. park/unpark可以先unpark,wait/notify不能先notify
  4. park不会释放锁,wait会释放锁

Q2:park/unpark的许可是如何工作的?

:每个线程有一个关联的许可(permit),许可只有0或1两种状态。unpark()将许可设置为1,park()消耗许可(1→0)。如果有许可,park()不阻塞;如果无许可,park()阻塞。多次unpark()不会累加许可。

Q3:为什么先unpark再park不会阻塞?

:因为unpark()会将许可设置为1。之后调用park()时,检测到有许可,就消耗许可并继续执行,不会阻塞。这类似于"提前消费"——先给你一个许可,你后面来使用时就不用等待了。

Q4:park会释放synchronized锁吗?

:不会。park只是阻塞当前线程,不会释放任何锁。这与wait()不同,wait()会释放对象锁。

Q5:如何正确处理park被interrupt的情况?

  1. park被interrupt唤醒后,中断标记会是true
  2. 中断标记为true时,park会失效(不阻塞)
  3. 需要用Thread.interrupted()清除中断标记,才能再次正常park
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐