知识图库
Java知识库
JDK线程池实现原理
Java中的强、软、弱、虚引用
深入拆解Java虚拟机
01 开篇词 | 为什么我们要学习Java虚拟机?
02 Java代码是怎么运行的?
03 Java的基本类型
04 Java虚拟机是如何加载Java类的?
05 JVM是如何执行方法调用的?(上)
06 JVM是如何执行方法调用的?(下)
7 JVM是如何处理异常的?
Java面试常见问题整理
Java面试常见问题-Java 基础篇
Java面试常见问题-Jvm篇
Java面试常见问题-并发篇
Android知识库
Kotlin编程第一课
1 开篇词 | 入门Kotlin有多容易,精通Kotlin就有多难
2 Kotlin基础语法:正式开启学习之旅
3 面向对象:理解Kotlin设计者的良苦用心
4 Kotlin原理:编译器在幕后干了哪些“好事”?
5 实战:构建一个Kotlin版本的四则运算计算器
6 object关键字:你到底有多少种用法?
7 扩展:你的能力边界到底在哪里?
8 高阶函数:为什么说函数是Kotlin的“一等公民”?
9 实战:用Kotlin写一个英语词频统计程序
10 加餐一 | 初识Kotlin函数式编程
11 委托:你为何总是被低估?
12 泛型:逆变or协变,傻傻分不清?
13 注解与反射:进阶必备技能
14 实战:用Kotlin实现一个网络请求框架KtHttp
15 加餐二 | 什么是“表达式思维”?
16 加餐三 | 什么是“不变性思维”?
17 加餐四 | 什么是“空安全思维”?
18 春节刷题计划(一)| 当Kotlin遇上LeetCode
19 春节刷题计划(二)| 一题三解,搞定版本号判断
20 春节刷题计划(三)| 一题双解,搞定求解方程
21 春节刷题计划(四)| 一题三解,搞定分式加减法
22 什么是“协程思维模型”?
23 如何启动协程?
24 挂起函数:Kotlin协程的核心
25 Job:协程也有生命周期吗?
26 Context:万物皆为Context?
27 实战:让KtHttp支持挂起函数
28 期中考试 | 用Kotlin实现图片处理程序
29 题目解答 | 期中考试版本参考实现
30 Channel:为什么说Channel是“热”的?
31 Flow:为什么说Flow是“冷”的?
32 select:到底是在选择什么?
33 并发:协程不需要处理同步吗?
34 异常:try-catch居然会不起作用?坑!
35 实战:让KtHttp支持Flow
36 答疑(一)| Java和Kotlin到底谁好谁坏?
37 集合操作符:你也会“看完就忘”吗?
38 协程源码的地图:如何读源码才不会迷失?
39 图解挂起函数:原来你就是个状态机?
40 加餐五 | 深入理解协程基础元素
41 launch的背后到底发生了什么?
42 Dispatchers是如何工作的?
43 CoroutineScope是如何管理协程的?
44 图解Channel:如何理解它的CSP通信模型?
45 图解Flow:原来你是只纸老虎?
46 Java Android开发者还会有未来吗?
47 Kotlin与Jetpack简直是天生一对!
48 用Kotlin写一个GitHub Trending App
49 结课测试 | “Kotlin编程第一课”100分试卷等你来挑战!
50 结束语 | 不忘初心
Android Framework 教程—基础篇
01 Ubuntu 使用快速入门
02 Make 构建工具入门
03 理解 Unicode UTF-8 UTF-16 UTF-32
04 Linux Shell 脚本编程入门1——核心基础语法
05 SeAndroid 使用极速上手
06 理解 C++ 的 Memory Order
07 AOSP 极速上手
08 系统开发工具推荐
09 添加 Product
运动相关知识
爱上跑步
01 开篇词 | 跑步,不那么简单的事儿
02 跑两步就喘了,是不是我不适合跑步?
03 正确的跑步姿势是什么样的?
04 为什么跑步要先热身?
05 怎样制定你的第一个10公里跑步计划?
06 快跑和慢跑,哪个更燃脂?
07 普通跑步者应该如何选择跑鞋?
08 买跑步装备,不要踩这些坑儿
09 跑步前到底应不应该吃东西?
10 跑步到底伤不伤膝盖?
11 参加了20场马拉松,我是如何准备的?
12 除了马拉松,还能参加哪些跑步赛事?
13 热点问题答疑 :跑完第二天浑身疼,还要不要继续跑?
健身房计划
[DeepSeek]减脂塑形计划
【DeepSeek】训练周期安排
每日餐饮热量控制
减脂期间食物推荐避坑指南
HarmonyOS知识库
其他知识类目
心理学相关
如何学点心理学——关于非专业人士学心理学的一点建议
投射性认同
-
+
首页
Java面试常见问题-并发篇
1. ### 通常创建线程有几种方式? - #### 创建线程的常用四种方式: - 继承 Thread 类 - 实现 Runnable 接口 - 实现 Callable 接口(JDK1.5>=) - 线程池方式创建 >i 通过继承 Thread 类或者实现 Runnable 接口、Callable 接口都可以实现多线程,不过实现 Runnable 接口与实现 Callable 接口的方式基本相同,只是 Callable 接口里定义的方法返回值,可以声明抛出异常而已。因此将实现 Runnable 接口和实现 Callable 接口归为一种方式。 - #### 采用实现 Runnable、Callable 接口的方式创建线程的优缺点 - **优点**: 线程类只是实现了 Runnable 或者 Callable 接口,还可以继承其他类。这种方式下,多个线程可以共享一个 target 对象。所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 - **缺点**: 编程稍微复杂一些,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法 - #### **采用继承 Thread 类的方式创建线程的优缺点**: - **优点**: 编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获取当前线程 - **缺点**: 因为线程类已经继承了 Thread 类,Java 语言是单继承的,所以就不能再继承其他父类了。 2. ### 说说线程的生命周期 先来看一张图:  这六个状态就对应线程的生命周期。下图为线程对应状态以及状态出发条件:  3. ### 说说 synchronized 的使用和原理 - #### 线程同步中的锁对象 - 对于普通同步方法,锁是当前实例对象 - 对于静态同步方法,锁是当前类 Class 对象 - 对于同步方法块,锁是 Synchronized 括号里配置的对象(对象为普通对象则锁定的是该对象,对象为 Class 对象则锁定的是 Class 对象) JVM 基于进入和退出 Monitor 对象来显示方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用 monitorenter 和 monitorexit 指令实现的,而方法同步是使用另外一种方式实现的。 monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法的结束处和异常处,JVM 保证每个 monitorenter 必须有对应的 monitorexit 与之配对。 4. ### synchronized 和 ReentrantLock 区别 - synchronized 是关键字,ReentrantLock 是 JUC 下面的一个类。 - JDK 1.5 之前同步锁只有 synchronized。 - 都是可重入的同步锁。 - synchronized 只有非公平锁,ReentrantLock 默认为非公平锁,但是可以手动设置为公平锁。 - ReentrantLock 需要手动释放锁 `try{--objectLock.lock();}---finally--{objectLock.unlock();}`,synchronized 隐形释放(方法或者代码块执行完、异常)。 - ReentrantLock 可中断,synchronized 不可中断,一个线程引用锁的时候,别的线程只能阻塞等待。 - ReentrantLock 和 synchronized 持有的对象监视器不同。 - ReentrantLock 能够将 wait/notify/notifyAll 对象化。synchronized 中,锁对象的 wait 和 notify() 或 notifyAll() 方法可以实现一个隐含的条件。 - synchronized 和 ReentrantLock 的性能不能一概而论,早期版本 synchronized 在很多场景下性能相差较大,在后续版本进行了较多改进,在低竞争场景中表现可能优于 ReentrantLock。 - ReentrantLock 提供了很多实用的方法,能够实现很多 synchronized 无法做到的细节控制,比如可以控制 fairness,也就是公平性,或者利用定义条件等。但是,编码中也需要注意,必须要明确调用 unlock() 方法释放,不然就会一直持有该锁。 5. ### 什么是线程安全? 按照《Java 并发编程实战》(Java Concurrency in Practice) 的定义就是:线程安全是一个多线程环境下正确性的概念,也就是保证多线程环境下共享的、可修改的状态的正确性,这里的状态反映在程序中其实可以看作是数据。 通俗易懂的说法:当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替执行,我们在主程序中不需要做任何同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。 6. ### 线程安全需要保证几个基本特征 - **原子性**: 简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。 - **可见性**: 是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。 - **有序性**: 是保证线程内串行语义,避免指令重排等。 7. ### 说一下线程之间是如何通信的? 线程之间的通信有两种方式:共享内存和消息传递。 - #### 共享内存 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写 - 读内存中的公共状态来隐式进行通信。典型的共享内存通信方式,就是通过共享对象进行通信。  例如上图线程 A 与 线程 B 之间如果要通信的话,那么就必须经历下面两个步骤: 1. 线程 A 把本地内存 A 更新过的共享变量刷新到主内存中去; 2. 线程 B 到主内存中去读取线程 A 之前更新过的共享变量。 - #### 消息传递 在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信。在 Java 中典型的消息传递方式,就是 wait() 和 notify(),或者 BlockingQueue。  8. ### 说说你对 volatile 的理解  9. ### 说一下 volatile 和 synchronized 的区别? - volatile 本质是在告诉 JVM 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取。synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 - volatile 仅能使用在变量级别。synchronized 则可以使用在变量、方法、和类级别的。 - volatile 仅能实现变量的修改可见性,不能保证原子性。而 synchronized 则可以保证变量的修改可见性和原子性。 - volatile 不会造成线程的阻塞。synchronized 可能会造成线程的阻塞。 - volatile 标记的变量不会被编译器优化。synchronized 标记的变量可以被编译器优化。 10. ### Thread 调用 run 方法和调 start 方法的区别? - 调用 run 方法不会再启一个线程,跟着主线程继续执行,和调普通类的方法一样; - 调用 start 方法表示启动一个线程。 #### 面试扩散 下面代码将输出什么内容?不清楚的建议自己去试试。 ```java public class ThreadDemo { public static void main(String[] args) { Thread thread=new Thread(new Runnable() { @Override public void run() { System.out.println("test start"); } }); thread.start(); thread.start(); } } ``` 11. ### 说一下 Java 创建线程池有哪些方式? - 通过 java.util.concurrent.Executors 来创建以下常见线程池:  - 也可以通过 java.util.concurrent.ThreadPoolExecutor 来创建自定义线程池,其中核心的几个参数: ```java int corePoolSize, //核心线程数量 int maximumPoolSize, //最大线程数 long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间 TimeUnit unit, //存活时间单位 BlockingQueue<Runnable> workQueue, //保存执行任务的队列 ThreadFactory threadFactory,//创建新线程使用的工厂 RejectedExecutionHandler handler //当任务无法执行的时候的处理方式 ``` 12. ### 线程池原理:  13. ### 说说 ThreadLocal 底层原理是什么,怎么避免内存泄漏?  #### 推荐阅读:[ThreadLocal 面试六连问,中高级必问](https://mp.weixin.qq.com/s/VrFarzkLQqdQskXO5MxW0g) 14. ### 说说你对 JUC 下并发工具类 - #### Semaphore 是一种新的同步类,它是一个计数信号。从概念上讲,信号量维护了一个许可集合。 - 如有必要,在许可可用前会阻塞每一个 acquire() 方法,然后再获取该许可。 - 每个 release() 方法,添加一个许可,从而可能释放一个正在阻塞的获取者。 - 但是,不使用实际的许可对象,Semaphore 只对可用许可的数量进行计数,并采取相应的行动。 信号量常常用于多线程的代码中,比如数据库连接池。  - #### CountDownLatch 字面意思是减小计数(CountDown)的门闩(Latch)。它要做的事情是,等待指定数量的计数被减少,意味着门闩被打开,然后进行执行。 CountDownLatch 默认的构造方法是 CountDownLatch(int count),其参数表示需要减少的计数,主线程调用 await() 方法告诉 CountDownLatch 阻塞等待指定数量的计数被减少,然后其它线程调用 CountDownLatch 的 CountDown() 方法,减小计数(不会阻塞)。等待计数被减少到零,主线程结束阻塞等待,继续往下执行。 - #### CyclicBarrier 字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。 CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await() 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞,直到 parties 个线程到达,结束阻塞。 15. ### CyclicBarrier 和 CountdownLatch 有什么区别? - #### CyclicBarrier 可以重复使用,而 CountdownLatch 不能重复使用。 CountdownLatch 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作。可以向 CountdownLatch 对象设置一个初始的数字作为计数值,任何调用这个对象上的 await() 方法都会阻塞,直到这个计数器的计数值被其他的线程减为 0 为止。所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次 —— 计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。 **CountdownLatch 的一个非常典型的应用场景是:**有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个 CountdownLatch 对象的 #await() 方法,其他的任务执行完自己的任务后调用同一个 CountdownLatch 对象上的 countDown() 方法,这个调用 #await() 方法的任务将一直阻塞等待,直到这个 CountdownLatch 对象的计数值减到 0 为止。 CyclicBarrier 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点(common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。
嘿手大叔
2024年12月24日 11:17
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码