并发面试合集

news2024/11/19 9:29:08

1.创建线程的方式

区分线程和线程体的概念,线程体通俗点说就是任务。创建线程体的方式:像实现Runnable、Callable接口、继承Thread类、创建线程池等等,这些方式并没有真正创建出线程,严格来说,Java就只有一种方式可以创建线程,那就是通过new Thread().start()创建。
而所谓的Runnable、Callable……对象,这仅仅只是线程体,也就是提供给线程执行的任务,并不属于真正的Java线程,它们的执行,最终还是需要依赖于new Thread()……

比如我们以new Runnable接口:

我们自定义了一个Runnable接口,通过主线程直接run,接着创建一个新线程并执行对应的任务。
注意:如果在这行的下面让主线程睡一会,再对这个runnable1任务调用run方法,JVM 可能会忽略第二次调用 run() 方法,尤其是在线程已经执行完毕的情况下。
其他的方法都差不多,本质都是只创建了任务(线程体),而不是线程。

线程是执行线程体的容器,线程体是一个可运行的任务

2.如何理解线程安全与不安全

线程安全和不安全是在多线程环境下对于同一份数据的访问是否能够保证其正确性和一致性的描述。

线程安全指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的正确性和一致性。

线程不安全则表示在多线程环境下,对于同一份数据,多个线程同时访问时可能会导致数据混乱、错误或者丢失。

//TODO 一些场景下判断是否线程安全,如果不安全,有什么手段让其变的线程安全?

3.(线程)死锁

在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。

那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁

概括:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止

public class deadLock {
    // 创建两把锁
    static final Object resource1 = new Object();
    static final Object resource2 = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized(resource1){
                System.out.println(Thread.currentThread().getName()+"持有resource1锁...");
                try {
                    sleep(1000);
                    synchronized(resource2){
                        System.out.println(Thread.currentThread().getName()+"持有resource2锁...");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally{
                    System.out.println(Thread.currentThread().getName()+"释放锁");
                }
            }
        }).start();
        new Thread(()->{
           synchronized(resource2){
               System.out.println(Thread.currentThread().getName()+"持有resource2锁");
               try {
                   sleep(1000);
                   synchronized(resource1){
                       System.out.println(Thread.currentThread().getName()+"持有resource1锁...");
                   }
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }finally{
                   System.out.println(Thread.currentThread().getName()+"释放锁");
               }
           }
        }).start();
    }
}

如何预防死锁? 资源有序分配法

线程 A 和 线程 B 获取资源(尝试获取锁)的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。Future

4.Future接口

用于异步,主要用在一些需要执行耗时任务的场景,避免程序一直原地等待耗时任务执行完成,执行效率太低。具体来说是这样的:当我们执行某一耗时的任务时,可以将这个耗时任务交给一个子线程去异步执行,同时我们可以干点其他事情,不用傻傻等待耗时任务执行完成。等我们的事情干完后,我们再通过 Future类获取到耗时任务的执行结果。这样一来,程序的执行效率就明显提高了。(多路复用?)
我有一个耗时任务,交给Future去做,(期间我可以做其他事,我也可以去查看任务是否完成,是否被取消等等),将来我直接去找Future要任务的执行结果。

// V 代表了Future执行的任务返回值的类型
public interface Future<V> {
    // 取消任务执行
    // 成功取消返回 true,否则返回 false
    boolean cancel(boolean mayInterruptIfRunning);
    // 判断任务是否被取消
    boolean isCancelled();
    // 判断任务是否已经执行完成
    boolean isDone();
    // 获取任务执行结果
    V get() throws InterruptedException, ExecutionException;
    // 指定时间内没有返回计算结果就抛出 TimeOutException 异常
    V get(long timeout, TimeUnit unit)

        throws InterruptedException, ExecutionException, TimeoutExceptio

}

FutureTask(实现)类

同时实现了Future接口和Runnable接口,本身是个Task,因此也是个线程体(任务),可以作为参数放到Thread的构造器里,或者通过线程池的submit方法(返回值是Future)来配合使用
submit和execute的区别? execute方法无返回值

public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future<String> submit = executorService.submit(new Callable1());
        System.out.println(submit );//[Not completed]
        String result = submit.get(); // 需要用get方法阻塞拿任务执行结果,
                                     //这个submit只是个future引用,拿他没啥用
        System.out.println(result);  // 获取任务执行结果
        System.out.println(submit );//[Completed normally]
        // 休眠一秒钟,关闭线程池(不然这个线程不会被回收,服务不会关闭)
        sleep(1000);
        executorService.shutdown();
    }

 结论:线程池中的submit方法:对于一个任务,无论是实现Runnable接口、Callable接口还是一个FutureTask,底层都是封装为FutureTask,而FutureTask对象本身是实现Runnable接口的

FutureTask类本身的run方法,就是执行Runnable、Callable的实现类并获取返回结果的过程。

ExecutorService接口中submit方法归根结底还是要把你传入的对象封装成FutureTask对象,并通过FutureTask类的内部实现来获取结果的,无论是直接传入自己的Runnable、Callable实现类还是构建FutureTask对象传入,本质上都是通过FutureTask去实现,没有什么区别;

 CompletableFuture(实现)类

提出场景:Future在实际使用过程中存在一些局限性比如不支持异步任务的编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)、获取计算结果的 get() 方法为阻塞调用。
提供了函数式编程的能力,可以通过回调的方式处理计算结果

创建异步对象

参数如果没带Executor,则用ForkJoinPool.commonPool() 作为它的线程池执行异步代码

supplyAsync 有返回值 runAsync无返回值

static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

 创建回调

whenComplete可以处理正常和异常的计算结果,exceptionally处理异常情况。
BiConsumer<? super T,? super Throwable>可以定义处理业务

whenComplete 和 whenCompleteAsync 的区别:

前者当前线程继续执行,后者把任务提交给线程池

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action);
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor);

public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn);

线程串行化

thenApply 方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。

thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。

thenRun方法:只要上面的任务执行完成,就开始执行thenRun,只是处理完任务后,执行 thenRun的后续操作

带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

多任务组合

前者所有任务完成,后者有一个完成即可。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);

参考: 异步编排(CompletableFuture异步调用)-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/qq_33524158/article/details/107243344

5.原子类

定义:具有原子性操作特征的类 ,即该操作不可分割、不可中断。即使在多个线程同时执行时,该操作要么全部执行完成,要么不执行,不会被其他线程看到部分完成的状态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2178200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Arthas watch (方法执行数据观测)

文章目录 二、命令列表2.3 monitor/watch/trace/stack/tt 相关2.3.5 watch &#xff08;方法执行数据观测&#xff09;举例1:监控方法举例2&#xff1a;同时观察函数调用前和函数返回后 本人其他相关文章链接 二、命令列表 2.3 monitor/watch/trace/stack/tt 相关 2.3.5 watc…

王道-计组

4 设相对寻址的转移指令占4字节,其中第1、第2字节是操作码,第3、第4字节是相对位移量(用补码表示)。设当前PC的内容为2008H,要求转移到2001H的地址,则该转移指令第3、第4字节的内容应为______ 答案:A 解析:由于指令占4字节,取指令之后(PC)+4。第3、第4字节的内容为:2…

Linux基础(三):安装CentOS7(系统安装+桥接联网+换源)

1.分区设置 由于使用 GPT 的关系&#xff0c; 因此根本无须考虑主/延伸/逻辑分区的差异。CentOS 默认使用 LVM 的方式来管理你的文件系统。使用GPT进行分区&#xff1a; 开机管理程序&#xff08; boot loader&#xff09; 使用CentOS 7.x默认的grub2软件。 2.各种分区格式 …

《Linux从小白到高手》理论篇(四):Linux用户和组相关的命令

List item 本篇介绍Linux用户和组相关的命令&#xff0c;看完本文&#xff0c;有关Linux用户和组相关的常用命令你就掌握了99%了。Linux用户和组相关的命令可以分为以下六类&#xff1a; 一.用户和用户组相关查询操作命令&#xff1a; Id id命令用于显示用户的身份标识。常见…

【Android 14源码分析】Activity启动流程-1

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…

MySQL:存储引擎简介和库的基本操作

目录 一、存储引擎 1、什么是存储引擎&#xff1f; 2、存储引擎的分类 关系型数据库存储引擎&#xff1a; 非关系型数据库存储引擎&#xff1a; 分布式数据库存储引擎&#xff1a; 3、常用的存储引擎及优缺点 1、InnoDb存储引擎 2、MyISAM存储引擎 3、MEMORY存储引擎 …

android kotlin Extension扩展函数

1、新建一个kt文件&#xff1a; 2、代码&#xff1a; class User(var name:String)/**扩展函数**/ fun User.Print(){print("用户名 $name") }// 扩展函数 swap,调换不同位置的值 fun MutableList<Int>.swap(index1: Int, index2: Int) {val tmp this[index1…

组合逻辑元件与时序逻辑元件

组合逻辑元件和时序逻辑元件都是数字电路中的基本构建块&#xff0c;但它们在功能和结构上存在显著差异。 1. 组合逻辑元件: 内容: 组合逻辑元件的输出仅取决于当前的输入&#xff0c;而与之前的输入无关。 它们没有记忆功能。 常见的组合逻辑元件包括&#xff1a; 与门 (AND…

Java | Leetcode Java题解之第437题路径总和III

题目&#xff1a; 题解&#xff1a; class Solution {public int pathSum(TreeNode root, int targetSum) {Map<Long, Integer> prefix new HashMap<Long, Integer>();prefix.put(0L, 1);return dfs(root, prefix, 0, targetSum);}public int dfs(TreeNode root,…

红帽RHCE和RHCA有什么区别

在红帽认证体系当中&#xff0c;RHCE&#xff08;Red Hat Certified Engineer&#xff09;以及 RHCA&#xff08;Red Hat Certified Architect&#xff09;乃是两项极具声望的高级认证。众多人士对于它们彼此之间存在的区别&#xff0c;深感困惑不解。接下来&#xff0c;IT 小编…

UE4_Niagara基础实例—3、使用自定义模块二

效果&#xff1a; 粒子限制在球形范围内 操作步骤&#xff1a; 1、因为我们要对粒子的位置进行控制&#xff0c;所以需要开启本地空间&#xff0c;粒子与发射器位置有关。使用GPU计算模拟&#xff0c;需勾选固定边界。 2、简单调节其它参数&#xff1a; 3、现在我想把粒子锁定…

【Linux的内存管理】

为什么需要内存管理 分段和分页内存分段内存分页 分页情况下&#xff0c;虚拟内存如何映射到物理地址页表原理多级页表 TLB快表段页式内存管理需要为什么进程地址空间Linux的进程虚拟地址空间管理进程地址空间如何分配虚拟内存虚拟内存的管理程序编译后的二进制文件如何映射到虚…

《Zeotero的学习》

学习视频链接 Zeotera的安装 官网点击download&#xff0c;选择合适的版本进行下载&#xff0c;并安装插件。 下载完成之后&#xff0c;点击安装包&#xff0c;一路默认就可以。如果不想下载在C盘&#xff0c;可以在步骤中选择自定义路径。 Zeotero的注册 官网进行注册&am…

AIGC: 从两个维度快速选择大模型开发技术路线

在当今人工智能飞速发展的时代&#xff0c;大模型开发技术路线的选择至关重要。本文将从两个维度出发&#xff0c;为大家快速介绍不同的大模型开发技术路线&#xff0c;帮助你在开发过程中做出明智的决策。 一、两个维度解析 传入大模型的信息 低要求&#xff1a;传入的信息相…

【D3.js in Action 3 精译_025】3.4 让 D3 数据适应屏幕(中)—— 线性比例尺的用法

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

Activity的生命周期分析

目录 Activity的生命周期全面分析 典型情况下的生命周期分析 异常情况下的生命周期分析 情况1&#xff1a;资源相关的系统配置发生改变导致Activity被杀死并重新创建 Activity的生命周期全面分析 在Android开发中&#xff0c;Activity的生命周期是非常重要的概念。它描述了…

数仓建模:DataX同步Mysql数据到Hive如何批量生成建表语句?| 基于SQL实现

目录 一、需求 二、实现步骤 1.数据类型转换维表 2.sql批量生成建表语句 三、小结 如果觉得本文对你有帮助&#xff0c;那么不妨也可以选择去看看我的博客专栏 &#xff0c;部分内容如下&#xff1a; 数字化建设通关指南 专栏 原价99&#xff0c;现在活动价39.9&#x…

前端使用xlsx-js-style导出Excel,带样式,并处理合并单元格边框显示不全和动态插入表头解决

一、在学习之前&#xff0c;先给出一些学习/下载地址&#xff1a; xlsx-js-style下载地址 https://github.com/gitbrent/xlsx-js-style 或者 https://www.npmjs.com/package/xlsx-js-style SheetJS中文教程&#xff1a; https://xlsx.nodejs.cn/docs/csf/cell 二、先看样…

双指针---(部分地更新)

双指针 复写零 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改&#xff0c;不要从函数返回任何东西。 …

Tableau 瀑布图应用示例

通过探索 10 个示例&#xff0c;将瀑布图的应用拓展到更深层次的业务分析&#xff01; 作为一种直观展示数据变化的图表&#xff0c;瀑布图被广泛应用在业务分析中。同时&#xff0c;借助 Tableau 2024.2 中的 Viz Extensions&#xff0c;如今我们可以快速在 Tableau 中实现瀑布…