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

相关推荐
じ☆ve 清风°2 小时前
JavaScript 原型与原型链:深入理解 __proto__ 和 prototype 的由来与关系
开发语言·javascript·原型模式
BillKu2 小时前
Java + Spring Boot + Mybatis 实现批量插入
java·spring boot·mybatis
YuTaoShao2 小时前
Java八股文——集合「Map篇」
java
有梦想的攻城狮4 小时前
maven中的maven-antrun-plugin插件详解
java·maven·插件·antrun
_r0bin_6 小时前
前端面试准备-7
开发语言·前端·javascript·fetch·跨域·class
zhang98800006 小时前
JavaScript 核心原理深度解析-不停留于表面的VUE等的使用!
开发语言·javascript·vue.js
硅的褶皱7 小时前
对比分析LinkedBlockingQueue和SynchronousQueue
java·并发编程
MoFe17 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
季鸢8 小时前
Java设计模式之观察者模式详解
java·观察者模式·设计模式
Fanxt_Ja8 小时前
【JVM】三色标记法原理
java·开发语言·jvm·算法