[JAVAEE] 面试题(三) - Callable接口, ReentrantLock类, Semaphore信号量, CountDownLatch类

目录

[一. Callable接口](#一. Callable接口)

[1.1 Callable接口介绍](#1.1 Callable接口介绍)

[1.2 Callable接口 与 Runnable接口](#1.2 Callable接口 与 Runnable接口)

[1.3 Callable接口的使用](#1.3 Callable接口的使用)

[二. ReentrantLock类](#二. ReentrantLock类)

[2.1 lock() unlock() trylock()](#2.1 lock() unlock() trylock())

[2.2 synchronized关键字 与 ReentrantLock类的区别](#2.2 synchronized关键字 与 ReentrantLock类的区别)

[三. Semaphore信号量](#三. Semaphore信号量)

[3.1 PV操作](#3.1 PV操作)

[3.2 二元信号量](#3.2 二元信号量)

[四. CountDownLatch类](#四. CountDownLatch类)

[4.1 CountDownLatch类的使用](#4.1 CountDownLatch类的使用)

[五. 总结](#五. 总结)


一. Callable接口

1.1 Callable接口介绍

Callable接口位于JUC(java.util.concurrent) 包中. 被注释**@FunctionalInterface** , 表示Callable接口是函数式接口 , Callable接口中有且仅有一个抽象方法.

1.2 Callable接口 与 Runnable接口

Runnable接口和Callable接口是并列关系, run方法与call方法并列.

Runnable中的run是任务的入口, 同理, Callable中的call也是任务的入口.

run没有返回值, call有返回值.

1.3 Callable接口的使用

Thread类没有提供构造方法来接受callable匿名类对象.
这时可以使用FutureTask对callable进行封装.

可以发现FutureTask类实现了RunnableFuture接口, RunnableFuture接口拓展了Runnable接口和Future接口. Thread类中提供了构造方法来接受FutureTask对象.FutureTask类中也提供了构造方法来接受Callable匿名对象.

**所以, 综上所述,**可以通过FutureTask来找到Callable.
案例: 使用Callable创建线程, 并将原子类count中的值增加为50000.

java 复制代码
private static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                for (int i = 0; i < 50000; i++) {
                    count.getAndIncrement();
                }
                return count.get();
            }
        };
//        Thread thread = new Thread(callable);
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();

        System.out.println(futureTask.get()); // get没有得到结果之前会一直阻塞, 获得结果为止.
    } 

二. ReentrantLock类

2.1 lock() unlock() trylock()

lock(): 加锁

unlock(): 解锁 (需要注意unlock不被调用的问题, 也就是线程提前终止)

trylock(): 不会阻塞, 加锁成功,返回true; 加锁失败, 返回false. 调用者根据返回值来决定下一步该做什么.

案例: 使用reentrantLock加锁, 来实现线程安全.

java 复制代码
    static int count = 0;
    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock(true);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                reentrantLock.lock();
                try {
                    count++;
                } finally {
                    reentrantLock.unlock();
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                reentrantLock.lock();
                try {
                    count++;
                } finally {
                    reentrantLock.unlock();
                }
            }
        });

        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(count);
    }

2.2 synchronized关键字 与 ReentrantLock类的区别

  1. synchronized是关键字 (是JVM内部通过C++实现的), ReentrantLock是JUC中的类.

  2. synchronized通过代码块 来控制加锁和解锁, ReentrantLock通过lock, trylock, unlock来控制加锁和解锁.

  3. synchronized是非公平锁, ReentrantLock默认是非公平锁, 可以设置为公平锁.

  1. ReentrantLock除了提供lock, unlock外, 还提供了trylock. (不会阻塞, 加锁成功返回true, 加锁失败返回false, 调用者根据返回值来决定下一步做什么)

5. ReentrantLock搭配的等待通知机制, 是Condition类, 比wait/notify功能更强大.


三. Semaphore信号量

Semaphore(信号量), 描述了某种可用资源的个数.

3.1 PV操作

申请一个资源, 计数器就会 +1. acquire

释放一个资源, 计数器就会 -1. release

注意: 可用资源个数为0时, 再继续申请, 就会阻塞.

3.2 二元信号量

可用资源个数为1, 申请资源相当于加锁, 释放资源相当于解锁.

(利用了可用资源个数为0时, 再继续申请, 就会阻塞).

java 复制代码
    static int count = 0;
    public static void main(String[] args) throws InterruptedException{
        Semaphore semaphore = new Semaphore(1);
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                count++;
                semaphore.release();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                try {
                    semaphore.acquire(); // 可用资源个数为0时, 再继续申请, 阻塞.
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                count++;
                semaphore.release();
            }
        });

四. CountDownLatch类

使用多线程, 经常将一个大任务拆分成多个小任务, 从而提升程序的运行效率.

4.1 CountDownLatch类的使用

  1. 构造方法指定参数, 表示拆分成了几个小任务.
  1. 每个小任务执行完毕后, 都调用一次countDown方法(当调用2次时, 说明小任务都执行完毕)
  1. 主线程中调用 await 方法, 所有任务执行完后, 结束阻塞.


示例:


五. 总结

  1. Callable接口和Runnable接口是并列关系, call方法与run方法都是任务入口, call有返回值, run没有返回值.

  2. 通过FutureTask类找到Callable匿名对象.(线程和任务解耦和).

  3. ReentrantLock类中的常用方法, lock, unlock, trylock.

  4. ReentrantLock类与synchronzied关键字的区别.

  5. Semaphore信号量, 描述某种可用资源的个数.

  6. 二元信号量, 模拟实现锁的效果.

  7. CountDownLatch类, 描述小任务的个数, 决定何时结束阻塞.

相关推荐
手握风云-5 分钟前
JavaEE初阶第十二期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(十)
java·开发语言·java-ee
盖世英雄酱5813612 分钟前
加了锁,加了事务 还是重复报名❓
java·数据库·后端
Pigwantofly15 分钟前
SpringAI入门及浅实践,实战 Spring‎ AI 调用大模型、提示词工程、对话记忆、Adv‎isor 的使用
java·大数据·人工智能·spring
淮北49429 分钟前
C++学习(线程相关)
jvm·学习
awonw1 小时前
[python][flask]Flask-Principal 使用详解
开发语言·python·flask
潼心1412o1 小时前
C语言(长期更新)第6讲:函数
c语言·开发语言
微笑听雨1 小时前
Java 设计模式之单例模式(详细解析)
java·后端
微笑听雨1 小时前
【Drools】(二)基于业务需求动态生成 DRL 规则文件:事实与动作定义详解
java·后端
猫猫的小茶馆1 小时前
【STM32】FreeRTOS 任务的删除(三)
java·linux·stm32·单片机·嵌入式硬件·mcu·51单片机
天天摸鱼的java工程师1 小时前
🔧 MySQL 索引的设计原则有哪些?【原理 + 业务场景实战】
java·后端·面试