JUC

可重入独占锁 ReentrantLock

可重入的意思是在同一线程中可用多次获取同一把锁而不阻塞,独占锁则是只能被一个线程使用。
如下代码所示,我们有8张票,要用10个线程去抢,肯定会出现两个线程抢不到,但是实际运行结果由于多个线程读到同一个值然后去执行业务操作,就会出现超卖的问题

public class Main {
    private static Integer stockCount = 8;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                buyStock();
            }, "thread-" + i).start();
        }
    }

    public static void buyStock() {
        if (stockCount > 0) {
            System.out.println(Thread.currentThread().getName() + "买到了票");
            stockCount--;
            System.out.println("剩余票数:" + stockCount);
        } else {
            System.out.println(Thread.currentThread().getName() + "抢票失败");
        }
    }
}

image
就可以使用ReentrantLock来解决这个问题,只要保证一个线程可以去抢票,其他线程等待即可。

package com.lyra;


import java.util.concurrent.locks.ReentrantLock;

public class Main {
    private static Integer stockCount = 8;
    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                buyStock();
            }, "thread-" + i).start();
        }
    }

    public static void buyStock() {
        lock.lock();
        try {
            if (stockCount > 0) {
                System.out.println(Thread.currentThread().getName() + "买到了票");
                stockCount--;
                System.out.println("剩余票数:" + stockCount);
            } else {
                System.out.println(Thread.currentThread().getName() + "抢票失败");
            }
        } finally {
            lock.unlock();
        }

    }
}

image
锁的使用规范文档中也记录了需要在finally中进行解锁避免锁一直不释放
image

ReentrantLock的newCondtion可以创建一个Condition对象对线程进行协调。
Condition的两个方法:

  • await: 将当前线程加入到Condition的等待队列中,线程暂停执行。
  • signal: 从Condition等待队列中唤醒一个线程。
  • signalAll: 唤醒等待队列中的所有线程。
    使用ReentrantLock可以很好的实现一个生产者消费者的模型,如下这个例子,生产者生产数据,每生产一个数据,那么唤醒消费者进行消费,当队列满了,那么就暂停生产,如果队列空了,那么暂停消费,暂停消费:
      Condition providerCondition = lock.newCondition();
        Condition consumerCondition = lock.newCondition();

        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
        AtomicInteger data = new AtomicInteger(0);

        // 生产者线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                while (true) {
                    lock.lock();
                    try {
                        if (queue.size() != 10) {
                            System.out.println("生产者生产数据:" + data);
                            data.getAndIncrement();
                            queue.add(data.getAcquire());
                            // 数据生产完成后唤醒消费者进行消费
                            consumerCondition.signalAll();
                        } else {
                            System.out.println("生产队列已满,等待消费");
                            // 如果队列满了,那么生产者可以等待一下下
                            providerCondition.await();
                            providerCondition.signal();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } finally {
                        lock.unlock();
                    }
                }
            }, "provider-" + i).start();
        }


        // 消费者消费
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                while (true) {
                    lock.lock();
                    try {
                        if (!queue.isEmpty()) {
                            Integer ran = queue.poll();
                            System.out.println("消费者消费数据:" + ran);
                            // 如果数据消费完毕那么唤醒线程再进行生产
                            providerCondition.signalAll();
                        } else {
                            System.out.println("消费队列已空,等待生产");
                            // 队列空了,消费者暂停消费
                            consumerCondition.await();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } finally {
                        lock.unlock();
                    }
                }
            }, "consumer-" + i).start();
        }

image

信号量 Semaphore

限制线程同时访问资源的数量,一般用来做资源池限流操作。
可以多个线程同时访问信号量资源。
信号量相关的方法:

  • 构造方法: 资源数
  • tryAcquire: 尝试获取许可,如果获取不到则返回false
  • acquire: 获取许可,可以传参一次性获取多少个许可。
  • release: 释放许可,可以传参一次性释放多少个许可。

我们可以实现一个简单的限流程序,首先我们定义了同时有两个线程执行业务处理,我们初始化资源数为2,然后线程内部尝试获取许可,如果获取不到表示被限流了,如果获取到了那么就在执行业务操作。

       Semaphore semaphore = new Semaphore(2);

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    if (!semaphore.tryAcquire()) {
                        System.out.println("已被限流");
                    } else {
                        System.out.println("获取到资源,执行业务处理");
                    }
                } finally {
                    semaphore.release();
                }

            }).start();
        }

闭锁 CountDownLatch

允许线程等待,直到所有线程执行完成然后再继续执行,应用场景就是并行任务汇总,例如有些接口查询比较慢,可以根据操作来分线程进行执行,等到所有线程执行完成后统一进行返回。
他有以下几个方法:

  • 构造方法: 初始化计数
  • countDown: 线程计数-1
  • await: 等待计数为0,所有线程执行完成
    比如我有一个业务功能,为了节省查询效率,需要有两个线程同时执行完成业务操作,我们就可以同一让线程全部执行完成再进行返回。
     CountDownLatch countDownLatch = new CountDownLatch(2);

        new Thread(() -> {
            System.out.println("执行业务操作1");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("业务操作1执行完毕");
            countDownLatch.countDown();
        }).start();


        new Thread(() -> {
            System.out.println("执行业务操作2");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("业务操作2执行完毕");
            countDownLatch.countDown();
        }).start();


        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("执行完成");

image

posted @ 2025-09-10 00:42  RainbowMagic  阅读(6)  评论(0)    收藏  举报