Java进阶篇--LockSupport

目录

LockSupport简介

LockSupport方法介绍

代码示例


LockSupport简介

LockSupport位于java.util.concurrent.locks包下,可以用来实现线程的阻塞和唤醒操作。每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在线程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用unpark()方法使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。

LockSupport方法介绍

LockSupport类提供了阻塞和唤醒线程的方法,主要有以下几个方法:

阻塞线程

  1. void park(): 阻塞当前线程,如果调用unpark方法或者当前线程被中断,则从park()方法中返回。
  2. void park(Object blocker): 功能同方法1,但增加了一个Object参数,用于记录导致线程阻塞的阻塞对象,方便问题排查。
  3. void parkNanos(long nanos): 阻塞当前线程,最长不超过nanos纳秒,具有超时返回的特性,如果nanos为0,则会立即返回。
  4. void parkNanos(Object blocker, long nanos): 功能同方法3,增加了记录阻塞对象的参数。
  5. void parkUntil(long deadline): 阻塞当前线程,直到deadline时间点,以毫秒表示。
  6. void parkUntil(Object blocker, long deadline): 功能同方法5,增加了记录阻塞对象的参数。

唤醒线程

  1. 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 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 和条件变量实现生产者-消费者模式,确保生产者在队列满时等待,消费者在队列空时等待,并通过条件变量进行线程的唤醒操作。这种方式可以有效地保证生产者和消费者的同步和互斥,避免了普通的忙等待和竞争条件,提高了系统的效率和可靠性。

你可能感兴趣的:(Java进阶篇,java,开发语言)