本文目录如下:
四、并发编程
一、并发基础
线程 和 进程 的区别?
进程
是程序的一次 执行过程,是 程序执行 和 资源分配 的基本单位。线程
是一个比 进程 更小的 执行单位,多个 线程 共享 进程 的 堆、方法区 资源,线程切换 效率更高。
【每个线程 有自己的 程序计数器、虚拟机栈 和 本地方法栈】
在 Java 中,启动 main 函数 就相当于启动了一个 JVM 的进程,而 main 函数 所在的 线程 就是这个进程中的 一个线程,也称 主线程。
线程 有哪些状态 [理解至上]?
新建状态
:线程对象被 创建 后,就进入了 新建状态。就绪状态
:线程对象调用 start()方法 或 线程获取到锁 后进入了 就绪状态,等待 分配CPU。运行状态
:就绪状态 的线程 获取了CPU,开始 执行代码。阻塞状态
:阻塞的情况分三种:
- 1、
同步阻塞
:线程执行 synchronized 和 notify() 后但是 没有获取到锁 进入 同步阻塞。
- 2、
等待阻塞
:线程执行 wait()方法 后进入 等待阻塞。
- 3、
定时阻塞
:线程执行 sleep() 或 join()方法 后进入 阻塞状态。终止状态
Java 线程的六种状态?
新建状态
可运行状态
:包括 就绪 和 运行 两个状态。阻塞状态
:【同步阻塞】等待状态
:【等待阻塞】定时等待状态
:【定时阻塞】终止状态
项目中哪些地方用到了多线程?
异步处理
:发短信、发邮件 等。批量处理
定时任务
创建线程有哪几种方式?
- 继承
Thread类
: 重写run()
方法。- 实现
Runnable接口
: 重写run()
方法。- 实现
Callabe接口
: 重写call()
方法, 该接口有 返回值。
实现Runnable接口 比 继承Thread类 有哪些优势?
- 可以避免 Java 中的 单继承限制
- 线程池 只能放入实现 Runable接口 或 Callable接口 的线程,不能直接放入继承 Thread类 的线程。
线程的 run()和 start() 有什么区别?
run()
: 线程体。它包含了线程要执行的内容。start()
: 创建 新的线程,并且执行在 run()方法 里的代码。
直接调用 run()方法,只会把 run()方法 当作 普通方法 去执行。
线程 之间如何通信?
锁与同步
(ReentrantLock + Condition)等待/通知机制
(synchronized + wait() + notify())信号量
: 点击查看共享内存
什么是 死锁?如何 预防死锁?
死锁
是指 两个以上的进程 在运行时,由于 竞争资源 而造成的一种 互相等待 的现象,若无外力干预,它们都将无法推进下去。
预防死锁,只需要破坏 死锁 的产生的 必要条件 即可:
破坏请求与保持条件
:线程执行前 一次性申请 所有的资源。破坏不可剥夺条件
:如果申请不到 所有资源,就要 主动释放 已占有的资源。
二、同步 & 互斥【重要】
1、线程调度
多线程 如何保证 线程安全?
手动锁 + 同步
(ReentrantLock + Condition)自动锁 + 同步
(synchronized + wait() + notify())线程安全类
:如 Vector、Hashtable、ConcurrentHashMap等。
sleep() 和 wait() 有什么区别?
sleep()
是使线程 休眠,不会释放对象的锁。wait()
是使线程 等待, 释放对象的锁。sleep()
后 线程 进入 定时阻塞 状态,wait()
后 线程 进入 等待阻塞 状态。sleep()
是 Thread类 的 方法;wait()
是 Object类 的 方法。
sleep()方法 和 yield()方法 有什么区别?
作用:都是 暂停线程,释放 CPU,使得其他线程能够访问。
sleep()
等到 睡眠时间结束 可进入 就绪状态。yield()
释放 CPU 后立即进入 就绪状态。sleep()
给其他线程运行机会时 不考虑线程的优先级;yield()
只会给 相同优先级 或 更高优先级 的线程以运行的机会;
3、synchronized 关键字
synchronized 是什么?有什么用?
synchronized
是 Java 中的一个 关键字。synchronized
可以保证被它修饰的 方法 或 代码块 在任意时刻只能有 一个线程执行。
synchronized 作用于 普通方法、静态方法、代码块 有哪些区别?
主要区别在于 锁的对象 不同:
synchronized 实现原理?
synchronized
的内部是基于JVM内置锁
实现的。- 当一个线程进入一个 synchronized 区域时,需要先获取 JVM内置锁,并在结束时 自动释放锁。
同步方法 和 同步块,哪个是更好的选择?
同步块
是更好的选择,因为它 不会锁住 当前对象,而是锁住 传入的对象。(也可以让它锁 当前对象)同步方法
会锁住 当前对象 (锁住 当前对象 中全部 同步块 和 同步方法)。
4、volatile 关键字 & ThreadLocal
volatile 关键字有什么作用?
volatile
关键字 主要用于保证 变量 的 可见性 和 有序性。
可见性
:变量 被 volatile 修饰时,它会保证 修改的值 会立即更新到 主存,其他线程 可以立即读取到 最新的值。有序性
:被 volatile 修饰的 变量 不会被 重排序。
volatile 使用场景?
多个线程读,一个线程写的场合
:一个线程在修改 volatile 变量 时,其他线程 能够立即看到 最新结果。作为标记使用
synchronized 和 volatile 有什么区别?
位置不同
:synchronized
可以修饰 方法、代码块;volatile
只能给修饰 变量。效率不同
:synchronized
可能导致 线程阻塞;volatile
不会造成 线程阻塞。
synchronized 和 Lock 有什么区别 (谈谈 Java 中的 synchronized 关键字)?
synchronized
可以给 方法、代码块 加锁;Lock
只能给 代码块 加锁。synchronized
不需要手动 管理锁,不会造成死锁;Lock
需要 手动管理锁,使用不当会 造成死锁。
注:
synchronized
是 Java 内置 同步机制关键字。Lock
是个 Java接口;
ThreadLocal 有什么作用?
访问
ThreadLocal变量
的 每个线程 都会有一个这个变量的 本地拷贝,不会影响其他线程的 局部变量,实现了线程的 数据隔离。
原理
:ThreadLocal 在内部使用一个类似于 Map 的 ThreadLocalMap 来存储 每个线程 的 变量副本。注意
:ThreadLocal 局部变量 使用结束后,需要手动remove
删除,否则会造成内存泄漏
。
三、JUC、并发容器
什么是 JUC?
JUC
指的是 Java 的 并发工具包 (java.util.Concurrent),提供了多种 线程同步 和 线程通信 的工具类
。JUC 提供了一系列 线程安全工具:
手动锁 + 同步
(ReentrantLock + Condition)自动锁 + 同步
(synchronized + wait() + notify())线程安全类
:如 Vector、Hashtable、ConcurrentHashMap等。线程池工具类
什么是 公平锁、非公平锁?
公平锁
: 线程 按照申请锁的顺序 来 获取锁。非公平锁
: 线程 不按照申请锁的顺序 来 获取锁。
`
注:
sychronized
只支持 非公平锁,ReentrantLock
默认 是 非公平锁,但是支持 公平锁。
什么是 可重入锁?Java中有哪些可重入锁?
可重入锁
也叫递归锁
: 可递归调用 的 锁,在 外层 获取 对象的锁 之后,内层 仍然可以 再次获得该对象的锁,并且 不发生死锁。
可重入锁
:Lock
、ReentrantLock
和synchronized
都是 可重入锁。
public class MyReentrant{
Lock lock = new Lock();
public void outer(){
lock.lock();
inner(); // inner() 函数中也有加锁释放锁的行为
lock.unlock(); // 调用结束后仍可以继续执行本函数
}
public void inner(){
lock.lock();
//do something
lock.unlock();
}
}
什么是 CAS (典型的 乐观锁)?
CAS
(Compare And Swap):是一种乐观锁
机制,通过比较 内存中的值 与 期望值 是否相等来确定 是否执行交换操作。- 优点:性能很高。缺点:如果 冲突频繁,则 CPU 占用很高。
ReentrantLock 是什么?
ReentrantLock
是 Lock 接口 的一个 具体实现类,是一个 可重入锁,同时也实现了 公平锁 机制。ReentrantLock
内部使用了 AQS 来实现 锁资源的竞争,从而保证了 公平性。
什么是 AQS 锁机制?
AQS
是 JDK 自带的 锁机制,用于实现 公平锁。AQS原理
:线程通过 CAS 竞争 锁资源,没有竞争到 锁资源 的线程,会加入到 AQS的同步队列 里面,通过 自旋 不断地 尝试获取锁。AQS的同步队列
是一个 虚拟的双向队列,不存在队列实例,仅保存节点之间的 关联关系。
Java 中有哪些原子类?
基本类型:
AtomicInteger
AtomicLong
数组类型:
AtomicIntegerArray
AtomicLongArray
四、线程池 【重要】
什么是线程池? 线程池有什么优点?
什么是线程池:
线程池
:管理 多个 线程 的池子, 线程池 里的 线程 不断地从任务队列
中取出任务
执行。- 高并发场景下 大量 创建线程 很费时, 使用 线程池 可以 提高响应速度。
线程池的优点:
提高响应速度
:高并发场景下 大量 创建线程 很费时, 使用 线程池 可以 提高响应速度。统一的连接管理
: 提高 线程 的 可管理性。
说说 Java 线程池主要参数?
线程池可以通过
ThreadPoolExecutor
来创建,核心参数有:
corePoolSize
:核心线程数。maximumPoolSize
:最大线程数。workQueue
: 存放任务的 阻塞队列。handler
: 线程池的 饱和策略。
不重要的参数:
- keepAliveTime:非核心线程的 存活时间。
- unit: 指定 keepAliveTime 的 时间单位。
Java 有哪几种常见的线程池?
FixedThreadPool
:固定大小 的线程池。SingleThreadExecutor
:单线程 的线程池。CachedThreadPool
:可缓存 线程池。ScheduleThreadPool
:可定时 的线程池。
种类 | 核心线程数 | 最大线程数 | 队列长度 | 描述 |
---|---|---|---|---|
FixedThreadPool | n | n | 无限 | / |
SingleThreadExecutor | 1 | 1 | 无限 | / |
CachedThreadPool | 0 | 无限 | 0 | 动态增删线程 |
ScheduleThreadPool | n | 无限 | 无限 | 周期性执行任务 |
常见线程池的应用场景?
FixedThreadPool
:适用于 负载稳定,任务数量可预知 的场景。CachedThreadPool
:适用于 负载不稳定,可能会有 负载高峰 的场景。SingleThreadExecutor
:适用于 按顺序执行任务 的场景,任务会按照 提交顺序 执行。
什么是阻塞队列? 常见的 阻塞队列 有哪几种?
阻塞队列 (BlockingQueue)
:在 普通队列 上增加了 两个操作:
- 1.在队列为空时: 获取元素的线程会 等待 队列变为非空。
- 2.当队列满时: 存储元素的线程会 等待 队列可用。
【阻塞队列
中的 线程任务 都是Runnable接口
类型的,因为 executor.execute() 的参数类型只能是 Runnable接口】
常见的 阻塞队列:
LinkedBlockingQueue
: 基于 链表 的 无界阻塞队列,容量为 Integer.MAX_VALUE。
【FixedThreadPool、SingleThreadExecutor 使用】SynchronousQueue
: 无缓冲 的 阻塞队列 (同步队列)。
【CachedThreadPool 使用】DelayedWorkQueue
: 支持 延时获取数据 的 阻塞队列。
【ScheduledThreadPool 使用】
常用的 线程池 的 拒绝策略 有哪些?默认是哪个?
饱和策略 | 说明 |
---|---|
AbortPolicy | (默认策略) 丢弃此任务,抛出异常 |
DiscardPolicy | 丢弃此任务, 不抛出异常 |
DiscardOldestPolicy | 从队列里 删除最老的任务 |
线程池 中的 核心线程 和 非核心线程 有什么区别?
核心线程
: 线程池中 始终存在 的线程,非核心线程
:当 任务数量 超过了 核心线程 数量,并且 阻塞队列 满时,才会创建的线程就是 非核心线程。
什么时候触发 拒绝策略 | 线程任务 的 分配步骤?
CPU密集 与 IO密集 场景如何设置 核心线程数量?
CPU密集型
:核心线程数 = CPU核数 + 1IO密集型
:核心线程数 = CPU核数 * 2 + 1- +1的原因:一个 额外 的线程,可以确保 CPU 不会 中断工作。
如果任务 被阻塞的时间 大于 执行时间,即 IO密集型 任务,就需要创建 比处理器核心数大几倍数量的线程。
ThreadPoolExecutor 和 Executors 有什么区别?
线程池ThreadPoolExecutor和Executors详解
ThreadPoolExecutor
是创建 自定义线程池。Executors
是用于创建 常见线程池 的工具类,不能实例化。
线程池中 submit() 和 execute()方法有什么区别?
execute(Runnable x)
:没有返回值。无法判断 任务 是否成功完成。submit(Callable x)
:提供Future
类型的 返回值。可以用来判断 任务是否成功完成。
六、Java8新特性 - Lambda表达式、方法引用、Stream流
1 Lambda表达式【重要】
什么是 Lambda表达式
Lambda
表达式 是一种 轻量级 的 匿名函数。它的本质就是一个 语法糖,可以用 更少的代码 来实现 同样的功能,程序 编译 时,由 编译器 转换为 常规的代码。Lambda
表达式 的形式为:(参数) -> Java表达式
。
Lambda表达式 使用案例?
- 如果 接口 内部只有 一个方法,则可以用 Lambda表达式 来 实例化接口,其中,Lambda表达式 的作用是 重写接口的方法。
- 如果 方法 的 参数 是 只包含一个方法的接口,则可以直接用 Lambda表达式 作为该方法的入参。再简单一点,可以使用 方法引用。
2 方法引用
什么是 方法引用?
方法引用
:是一种快速简便地将 现有方法 转换为Lambda
表达式 的方式。- 它可以使 代码 更加 简洁易懂,减少重复性的代码,提高代码的 可读性 和 可维护性。
方法引用 使用案例?
3 Stream流
什么是 Stream流?
Stream
是一个 高效 且 易于使用 的 集合处理数据方式。
- 可以进行复杂的 集合数据操作,比如 查找、过滤 和 映射 等。
- 不直接修改 源数据,而是生成 新的 数据集合。
Stream流 使用案例?
List<Student> students = new ArrayList<>();
// ...
// 获取所有学生的姓名
List<String> tmpList1 = students.stream().map(s -> s.getName()).collect(Collectors.toList());
// 获取所有学生的姓名和年龄
List<People> tmpList2 = students.stream().map(s -> new People(s.getName(), s.getAge)).collect(Collectors.toList());
// 筛选年龄 大于16岁 的学生
List<Student> tmpList3 = students.stream().filter(s -> s.getAge() > 16).collect(Collectors.toList());