目录
LockSupport简介
LockSupport位于java.util.concurrent.locks包下,可以用来实现线程的阻塞和唤醒操作。每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在线程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用unpark()方法使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。
LockSupport方法介绍
LockSupport类提供了阻塞和唤醒线程的方法,主要有以下几个方法:
阻塞线程
- void park(): 阻塞当前线程,如果调用unpark方法或者当前线程被中断,则从park()方法中返回。
- void park(Object blocker): 功能同方法1,但增加了一个Object参数,用于记录导致线程阻塞的阻塞对象,方便问题排查。
- void parkNanos(long nanos): 阻塞当前线程,最长不超过nanos纳秒,具有超时返回的特性,如果nanos为0,则会立即返回。
- void parkNanos(Object blocker, long nanos): 功能同方法3,增加了记录阻塞对象的参数。
- void parkUntil(long deadline): 阻塞当前线程,直到deadline时间点,以毫秒表示。
- void parkUntil(Object blocker, long deadline): 功能同方法5,增加了记录阻塞对象的参数。
唤醒线程
- void unpark(Thread thread): 唤醒处于阻塞状态的指定线程。
需要注意的是,LockSupport的阻塞和唤醒线程功能是依赖于sun.misc.Unsafe类实现的。在阻塞线程方法中,可以使用带Object参数的重载方法来记录导致线程阻塞的阻塞对象,这有助于问题排查和定位。
相较于传统的synchronized等同步方式,LockSupport的线程阻塞不会引发死锁和竞争条件问题,并且可以精确地控制线程的阻塞和唤醒。当线程被阻塞时,其状态会变为WAITING;而使用synchronized等方式阻塞线程时,线程的状态则会变为BLOCKED。
代码示例
以下是一个示例,展示了如何使用 LockSupport 和条件变量(Condition)来实现生产者-消费者模式:
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.LockSupport;
public class main {
private static final int CAPACITY = 5;
private static Queue<Integer> queue = new LinkedList<>();
private static Lock lock = new ReentrantLock();
private static Condition notFull = lock.newCondition();
private static Condition notEmpty = lock.newCondition();
public static void main(String[] args) {
Thread producer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
while (queue.size() == CAPACITY) {
notFull.await();
}
queue.offer(i);
System.out.println("生产者生产:" + i);
notEmpty.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
Thread consumer = new Thread(() -> {
while (true) {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int item = queue.poll();
System.out.println("消费者消费:" + item);
notFull.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
producer.start();
consumer.start();
// 让主线程等待一段时间
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 停止生产者和消费者线程
LockSupport.unpark(producer);
LockSupport.unpark(consumer);
}
}
在以上代码中,queue 是一个用于存储数据的队列,lock 是一个可重入锁,notFull 和 notEmpty 分别是条件变量,用于控制生产者和消费者的等待和唤醒操作。
生产者线程在每次生产之前,使用 lock.lock() 获取锁并进入临界区,然后通过 while 循环检查队列是否已满。如果队列已满,则调用 notFull.await() 将生产者线程阻塞,直到有消费者消费数据并唤醒生产者线程。一旦队列有空位,生产者将数据放入队列,并调用 notEmpty.signalAll() 唤醒等待的消费者线程。最后,释放锁并结束本次生产。
消费者线程在每次消费之前,也需要先获取锁并进入临界区,然后通过 while 循环检查队列是否为空。如果队列为空,则调用 notEmpty.await() 将消费者线程阻塞,直到有生产者生产数据并唤醒消费者线程。一旦队列有数据,消费者从队列中取出数据,并调用 notFull.signalAll() 唤醒等待的生产者线程。最后,释放锁并继续下一轮消费。
主线程启动生产者和消费者线程后,等待一段时间后调用 LockSupport.unpark(Thread) 方法来停止生产者和消费者线程。
这个示例展示了如何使用 LockSupport 和条件变量实现生产者-消费者模式,确保生产者在队列满时等待,消费者在队列空时等待,并通过条件变量进行线程的唤醒操作。这种方式可以有效地保证生产者和消费者的同步和互斥,避免了普通的忙等待和竞争条件,提高了系统的效率和可靠性。