Java并发编程
多线程初探:线程产生、同步、原理
提交到线程池的可以是:
- 实现了
Runnable接口的任务 - 实现了
Callable接口的任务 FutureTask类型的任务
而 FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。用于异步计算?因为可以之后再 get() 线程的结果.
object.wait() 必须在 synchronized 方法中使用,会自动放弃锁,直到别的线程 notify 才能进入 RUNNABLE 状态,等待线程调度。object.sleep() 并不会放弃锁
线程组的数据结构
1 | |
- 一个进程创建的所有线程,都是共享一个内存空间的;cpu 切换任务时机是在指令级,不在语言级别。同时还需要考虑编译器优化,指令重排对多线程访问有序性的影响。
- 多线程编程出现并发错误的源头在于:可见性问题(cpu 缓存是的不同线程读取到了未更新的数据)、有序性问题(编译器优化使得在不同时间片切换,线程访问错误)
- Happens-Before 规则:前面一个操作的结果对后续操作是可见的(这里指的都是对于共享变量的操作,是可见的)
1 | |
😭 指令重排(有序性问题)可能会导致顺序 分配内存(memory = allocate())、将 instance 指向这块内存(instance = memory)、 初始化对象(调用构造方法)。这导致其他线程可能访问了未初始化完全的变量。Java 使用了 volatile:禁止指令重排序 解决此问题。所以这个例子目前是可行的。
volatile 关键字怎么解决并发中的经典问题
禁止线程用自己的缓存,使得线程对变量的修改可以让所有的线程可见!和禁止指令重排,解决有序性问题
🤔JVM 会等待工作线程全部结束之后才会停止主线程服务?
是的,JVM 确实会等待,但并不意味着主线程 main 也会等待,这就是我们需要 join 的原因。join 会让主线程阻塞从而等待子线程优先于主线程结束,执行逻辑正确;否则主线程可能中途结束执行到了后面的逻辑。
同步原语 synchronized:
- static synchronized function()会在类级别的上加锁,整个 JVM 只有一把锁。
- 🤔 如何锁住其他对象呢?传递 object 对象作为参数,锁住 object。这也意味每次要锁住都必须传入相同的 object,会有难度
线程池:
- 关注 corePoolSize,线程池保证这部分线程始终能够接受任务,不会轻易回收。而 corePoolSize <= 线程数量<= MaxiumPoolSize 这部分非核心线程会在任务完成之后一段时间内被回收。
- 如果 MaxiumPoolSize 设置非常大,或者 workQueue 是无界队列,拒绝策略将不会生效,永远堆不满。
- ❌ 缓存(无线制队列和任务)、简单(只能一个)、定长线程池(core=maxium)都太极端,自定义的可以。
并发同步:锁、线程本地
⭐Lock 接口:
✅ 可重入锁:其本身含义是指 同一个线程可以多次获取已经持有的锁
ReentrantLock 有 lock() 、trylock() 方法,其中 trylock() 有返回值 boolean,true 时获得锁进入,立即返回。等待时间内获取到锁,trylock( long time )是一段时间的自旋锁。❓ 是不是可以缓解死锁的出现呢
注意,finally 语句中都要将锁释放,lock.unlock( )。
公平性上,默认的都是非公平锁,意味着线程不一定按照到达锁的时间按顺序排队获得锁,而是按照策略选择一个队列中的线程执行。
✅ 可重入读写锁:读锁是共享锁,多个线程可同时进入临界区;写锁是独占锁。
他们在公平性上,对于同一份资源,如果读锁在队列的头部,那么新的读线程可能会插队;反之,如果是写锁,那么读线程会排到队列的末尾。提高性能之举。
❓ 关于 reentrantlock 和 synchronized 关键字的对比
| 对比维度 | synchronized |
ReentrantLock |
|---|---|---|
| 所在包 | java 关键字,属于 JVM 语法级别 |
java.util.concurrent.locks 中的类 |
| 加锁/释放方式 | 自动加锁、自动释放(异常也会自动释放) | 需要手动加锁 lock() 和释放 unlock(),要写在 finally |
| 可重入性 | ✅ 是 | ✅ 是 |
| 公平锁支持 | ❌ 不支持,默认非公平 | ✅ 可设置为公平锁(先来先得) |
| 可中断锁 | ❌ 不支持 | ✅ 支持 lockInterruptibly() |
| 尝试获取锁(不阻塞线程) | ❌ 不支持 | ✅ 支持 tryLock() |
| 是否支持条件变量 | ❌ 不支持(只能用 wait()/notify()) |
✅ 支持 Condition 类,可实现多个等待队列 |
| 性能(JDK1.6 之后) | 已优化,引入偏向锁/轻量级锁,性能很好 | 高并发下性能优秀,功能灵活 |
| 可见性与原子性 | ✅ 内置保证(由 JVM 实现) | ✅ 依赖底层实现,效果相同 |
| 锁的粒度 | 隐式对象级别 | 灵活控制(可锁方法块、不同对象) |
| 使用难度 | ✅ 简单,关键字一写就好 | ⚠️ 稍复杂,需配合 try-finally 写法(finally 里面写上 unlock,防止并发错误) |
| reentrantlock 更适合可中断、限时等待、多条件变量的场景,当然 synchronized 关键字已经优化的很好了 |
并发流程控制
❓多个线程之间存在流程控制怎么办,考虑并发顺序和流程,而非并发安全问题!
| 特性 | CountDownLatch | CyclicBarrier | Semaphore |
|---|---|---|---|
| 等待机制 | 等待 N 次 countDown() |
等待 N 个线程 await() |
获取 N 个许可才可进入 |
| 可否重用 | ❌ 不可重用 | ✅ 可重用 | ✅ 可重用 |
| 回调功能(触发动作) | ❌ 没有 | ✅ 有(barrier action) | ❌ 没有 |
| 应用场景 | 等待其他线程完成(等所有学生到了再开班会,只关心人数是否齐全) | 线程“集结点”,到达屏障点之后再统一执行任务。可以一环扣着一环 | 控制并发访问,允许最多多少个线程同时进入临界区 |
| 控制的是谁 | 主线程等待子线程 | 所有线程互相等待 | 控制访问资源的线程数量 |
| 实现原理 | AQS 共享锁 | AQS + 屏障机制 | AQS 共享锁 + 许可证信号量 |
| 用法 | await( ) 和 countdown( )配合, await 用来等待 | await( )阻塞标记单个任务执行完毕,计数器-1 直到为 0 执行回调函数 |
✅CountDownLatch 适合协调多个线程完成某项任务(等待一组任务完成后再执行):
使用 await( ) countdown( ) api 用于同步。当 count 减少到 0 之后 await 阻塞。主线程调用 await() 等待其他线程调用 countDown()
✅CyclicBarrier 适合多线程并行执行后统一汇总(后面还有统一的任务):
❌threadlocal??
- 线程隔离作用:每个线程都有着自己的资源,避免线程之间的共享导致并发不安全。另外实现类:threadlocal 的 getter、setter、remove 方法
- 上下文特性,减少线程内同一个实例反复传递参数繁琐:

threadlocal 内存泄漏问题?
CAS 原子操作
CAS 基于乐观锁的思想实现,底层是硬件上的 compare and set,解决 “一个变量在多线程下原子修改” 问题。 涉及到 current value、expect value、set value,如果当前值和期望值相同,那么就会设置新的值。这个过程会保证自旋,设置自旋失败次数上限防止长时间占用锁。和悲观锁 synchroized reentrantLock 不同的是,cas 是多线程同时进入的,他们各自拿到内存的值,get compare if correct then set,而悲观锁确保在同一时间只能有一个线程拿到锁(非读锁、信号量)
❓ 是不是自旋一定会带来并发性能下降?不一定,如果临界区很小,线程不是很多,频繁的切换线程浪费更多。
| 类型 | 具体类 | 说明 |
|---|---|---|
| Atomic* 基本类型原子类 | AtomicInteger、AtomicLong、AtomicBoolean | 基本类型的原子自增操作 |
| Atomic*Array 数组类型原子类 | AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray | |
| Atomic*Reference 引用类型原子类 | AtomicReference(只能保证引用对象原子更新,但是内部字段不保证)、AtomicStampedReference、AtomicMarkableReference | 不替换整个对象的条件下,对字段进行原子更新。字段必须是 volatile,保证内存可见 |
| Atomic*FieldUpdater 升级类型原子类 | AtomicIntegerfieldupdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater | |
| Adder 累加器 | LongAdder、DoubleAdder | 使用分段锁,维护 Cell 数组中每个 cell 增加的值最终累加 |
| Accumulator 积累器 | LongAccumulator、DoubleAccumulator |
分段/分区来减少锁冲突,一种将资源拆成多段,每段各自加锁的并发策略。LongAdder 底层维护 cell 数组(根据当前的线程数确定数组的大小),每个线程只在各自的 cell 上累加,不会出现冲突。也是分治思想