【多线程进阶】JUC下的常用类

点进来你就是我的人了
博主主页:戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔


目录

ReentrantLock

Semaphore(信号量)

计数器——CountDownLatch



"JUC"是"Java Util Concurrent"的缩写,代表Java提供的一套并发工具类。这些工具类大大简化了编程并发和多线程应用的复杂性,提供了更高级、更强大、更安全的并发操作功能。以下是一些常见的JUC类:

  1. ExecutorServiceThreadPoolExecutorExecutors:这些类用于线程池的创建和管理。它们可以控制线程的数量,自动管理线程的生命周期,并提供将任务提交到线程池执行的方法。

  2. FutureCallableFuture表示异步计算的结果,Callable则是具有返回值的Runnable,用于在ExecutorService中执行任务。

  3. CountDownLatch:一个同步辅助类,在完成一组正在其它线程中执行的操作之前,它允许一个或多个线程一直等待。

  4. CyclicBarrier:它允许一组线程互相等待,直到所有线程都达到一个公共的屏障点。

  5. Semaphore:一个计数信号量,常用于限制可以访问某些资源(物理或逻辑的)的线程数目。

  6. ConcurrentHashMap:一个线程安全的HashMap。

  7. AtomicIntegerAtomicLongAtomicReference:原子变量类,用于实现无锁的线程安全操作。

  8. LockReentrantLockCondition:显示锁和条件对象,用于实现比synchronized更复杂的线程同步和通信。

此处主要介绍一下之前没有提到的类 (其它类的使用可以在我多线程的专栏查看)

ReentrantLock

ReentrantLock是标准库为我们提供的另一种锁,顾名思义。该锁也是可重入锁。
我们的Synchronized也是我们标准库提供的锁,那么它们有什么区别呢?
Synchronized: 是直接基于代码块进行加锁解锁的。
ReentrantLock:是使用了lock()方法和unlock()方法进行加锁解锁的。

方法 作用
lock() 加锁,获取不到就死等
trylock(time 超时时间) 加锁,如果一定时间内没有获取到就放弃
unlock() 解锁

我们的ReentrantLock在使用起来可能有一些需要注意的事项:

a. lock写在try之前;
b.一定要在finaly里面进行unlock();
  1. lock() 方法应在 try 代码块之前调用。这是因为,如果 lock() 方法在 try 代码块内并且发生异常,那么锁可能无法正确获取,而在 finally 代码块中,unlock() 方法仍会被执行。这可能会尝试释放并未真正被线程持有的锁,从而引发 IllegalMonitorStateException 异常。

  2. unlock() 方法应在 finally 代码块中调用,以确保锁一定会被释放,无论 try 代码块中的代码是否抛出异常。这是因为,如果 try 代码块中的代码抛出异常,并且 unlock() 不在 finally 中被执行,那么可能导致锁没有被正确释放,从而阻止其他线程获取该锁,进一步可能导致死锁现象。

上述是我们ReentrantLock的劣势,当然我们ReentrantLock也是有一些优势的。

1.我们的ReentrantKLock提供了公平锁版本的实现,我们的Synchronized只实现了非公平锁。

2. 我们Synchronized尝试加锁,如果锁已经被占有,就进行阻塞等待(死等),ReentrantLock提供了更灵活的获取锁的方式: trylock()

方法 作用
trylock() 无参数,能加锁就加,加不上就放弃
trylock(time) 有参数,超过指定时间,加不上锁就放弃

3.ReentrantLock提供了一个更方便的等待通知机制,Synchronized搭配的是wait,notify,当我们notify的时候是随即唤醒一个wait状态的线程。ReentrantLock搭配一个Condition类,进行唤醒的时候可以唤醒指定线程。


Semaphore(信号量)

Semaphore(信号量):信号量本质就是一个计数操作,描述"可用资源的个数",这里设计两个操作。
P操作:申请一个可用资源,计数器-1
V操作:释放一个可用资源,计数器+1

这里我们的信号量可以搭配着停车场来理解:
我们的停车场的大牌子上就写着,当前剩余车位xx个,当有车进去,就相当于进行了一次P操作,计数器-1,当有车从停车场出来,就相当于进行了一次V操作,计数器+1,当我们的停车场的剩余车位为0时,也就是计数器为0时,如果有车想停车,要么在这里等,要么就去其他停车场。
我们的Semaphore同样提供了两个方法:

方法 作用
acquire 申请资源
release 释放资源

我们在这里设计一个场景,我们图书馆一共有5本《算法导论》,但有10个同学去借

public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("我来借书了");
                    semaphore.acquire();
                    System.out.println("我拿到算法导论了!");
                    Thread.sleep(500); //借阅时间
                    System.out.println("我还书了");
                    semaphore.release();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable);
            t.start();
        }
    }

【多线程进阶】JUC下的常用类_第1张图片

我们的锁(可以认为是一个计数器为1的信号量),锁是信号量的特殊情况,信号量是锁的一般表达。
实际开发中,锁的出场频率是最高的,但也是有一些特殊的场景会用到信号量。


计数器——CountDownLatch

CountDownLatch是一个小组件,用于处理特殊场景下的。

就类似一场跑步比赛,开始时间是明确的,但结束时间是不确定的,只有所有选手冲线了才算结束。像这种场景,就引入了CountDownLatch.

方法 作用
CountDownLatch(人数) 在构造的时候,传入一个计数(参赛选手的个数)
await() 等待所有线程执行结束
countDown() 表示选手冲线

在开始时将CountDownLatch的计数器设定为参赛选手的数量,每个选手在跑完比赛后就调用countDown()方法将计数器减一。然后在比赛主管线程中,可以调用await()方法来等待所有选手完成比赛,也就是等待计数器变为0,此时比赛才算结束。 

调用await方法的线程,需要等待其他线程将计数器减为0,才能继续恢复执行。

import java.util.Random;
import java.util.concurrent.CountDownLatch;
 
/**
 * 使用CountDownLatch.await方法的线程会阻塞,直到所有等待线程全部执行结束为止
 * 大号进阶版本的join方法
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        // 等待线程需要等待的线程数,必须等这10个子线程全部执行完毕再恢复执行
        CountDownLatch latch = new CountDownLatch(10);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(new Random().nextInt(1000));
                    System.out.println(Thread.currentThread().getName() + "到达终点");
                    // 计数器 - 1
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(runnable, "运动员" + i);
            t.start();
        }
        // main线程就是裁判线程,需要等待所有运动员到终点再恢复执行
        // 直到所有线程调用countdown方法将计数器减为0继续执行
        latch.await();
        System.out.println("比赛结束");
    }
}

【多线程进阶】JUC下的常用类_第2张图片

你可能感兴趣的:(多线程篇,java,开发语言,多线程)