[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类, 描述小任务的个数, 决定何时结束阻塞.

相关推荐
草莓base3 分钟前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
Allen Bright16 分钟前
maven概述
java·maven
qystca18 分钟前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
编程重生之路18 分钟前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端
薯条不要番茄酱19 分钟前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
今天吃饺子23 分钟前
2024年SCI一区最新改进优化算法——四参数自适应生长优化器,MATLAB代码免费获取...
开发语言·算法·matlab
努力进修28 分钟前
“探索Java List的无限可能:从基础到高级应用“
java·开发语言·list
politeboy28 分钟前
k8s启动springboot容器的时候,显示找不到application.yml文件
java·spring boot·kubernetes
Daniel 大东1 小时前
BugJson因为json格式问题OOM怎么办
java·安全
Ajiang28247353042 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++