Java 并发编程
前言
并发编程可以总结为三个核心问题:
- 分工:高效拆解任务并分配给线程
- 同步:线程之间的合作
- 互斥:同一时刻只允许一个线程访问共享资源。
分工、合作、不抢打印机。
Java SDK 并发包很大部分内容都是按照这三个维度组织的,如:
- Fork/Join 框架就是一种分工模式。
- CountDownLatch 就是一种典型的同步方式。
- 可重入锁 就是一种互斥手段。
Java SDK 并发包不过是针对并发问题开发出来的解决问题的工具。
Java SDK 并发包其余部分则是:并发容器、原子类。(辅助工具)
问题背后的本质、起源,站在理论、模型的角度来看Java并发问题。这些问题可以用于其它语言。
怎么学好?
- 跳出来,看全景。
- 钻进去,看本质。
1. 跳出来,看全景
建立一张全景图
并发编程抽象成三个核心问题:
- 分工
- 同步
- 互斥
1.分工
Java SDK 并发包里的 Executor 、 Fork/Join、Future 本质上都是一种分工方法。
并发编程领域还总结了一些设计模式:生产者/消费者、Thread-Per-Message、Worker Thread模式。
2.同步
线程间的协作问题。
一个线程执行完了一个任务,如何通知执行后续任务的线程开始。
Java SDK 并发包里的 Executor 、 Fork/Join、Future 本质上都是一种分工方法,同时也能解决线程间协作的问题。
Java SDK 中的 CountDownLatch、CyclicBarrier、Phaser、Exchanger也是用来解决线程协作问题的。
线程协作问题都可以描述为:
- 当某个条件不满足时,线程需要等待。
- 当某个条件满足时,线程需要被唤醒执行。
Java 并发编程领域,解决协作问题的核心技术是管程。
管程是一种解决并发问题的能用模型。
管程是解决并发问题的万能钥匙。
这部分内容的学习,关键是理解管程模型。
3.互斥
分工、同步主要强调的是性能,并发编程还有一个问题需要关注:正确性。专业术语叫“线程安全”。
导致不确定性问题的原因是:
- 可见性问题
- 有序性问题
- 原子性问题
为了解决三个问题,Java语言引入了内存模型,内存模型提供了一系列的规则,利用这些规则,我们可以避免可见性问题,有序性问题,但还不足以完全解决线程安全问题。
解决线程安全问题的核心方案还是互斥。
互斥:指同一时刻,只允许一个线程访问共享变量(一个人使用打印机)。
实现互斥的核心技术就是锁, Java语言中的 synchronized、SDK里的各种Lock都能解决互斥问题。
虽然解决了安全性问题,但同时也带来了性能问题。
怎么保证安全性的同时,又尽可能提高性能呢?
可以分场景优化,Java SDK里提供的 ReadWriteLock、StampedLock 就可以优化读多写少场景下锁的性能。还可以使用无锁的数据结构,例如Java SDK里提供的原子类都是基于无锁技术实现的。
还有一些其它的方案,原理是不共享变量或者变量只允许读。这方面,Java提供了Thread Local和final 关键字,还有一种 Copy-on-write 的模式。
使用锁,除了注意性能问题,还需要注意死锁问题。
理解可见性:需要了解一些CPU和缓存的知识
理解原子性:需要理解一些操作系统的知识
无锁算法的实现:需要理解CPU缓存的知识
2. 钻进去,看本质
工程上的解决方案,一定要有理论做基础。
总结
并发知识要成体系的学习。