Java并发编程笔记--理论知识

 

第一讲:开篇

一、总览

1.分工
a、烧水泡茶
b、Java SDK 并发包 Executor、Fork/Join、Future
c、生产者-消费者、Thread-Per-Message、WorkerThread

2.同步
a、线程间的协作:当某个条件不满足时,线程需要等待,当某个条件满足时,线程需要被唤醒执行
b、Executor、Fork/Join、Future、CountDownLatch、CyclicBarrier、Phaser、Exchanger
c、Java 并发编程领域,解决协作问题的核心技术是管程

3.互斥
a、线程安全:并发程序里,当多个线程同时访问同一个共享变量的时候,结果是不确定的
b、所谓互斥,指的是同一时刻,只允许一个线程访问共享变量
c、实现互斥的核心技术就是锁,synchronized、各种Lock

对于某个具体的技术,我建议你探索它背后的理论本质,理论的应用面更宽,一项优秀的理论往往在多个语言中都有体现,在多个不同领域都有应用。
所以探求理论本质,既能加深对技术本身的理解,也能拓展知识深度和广度,这是个一举多得的方法。这方面,希望我们一起探讨,共同进步


二、理论知识
第一讲:可见性、原子性、有序性
1.可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性
每一个CPU都有一个缓存,线程修改的是自己缓存的数据

2.原子性:我们把一个或者多个操作在CPU执行的过程中不被中断的特性称为原子性

3.有序性:有序性指的是程序按照代码的先后顺序执行。编译器为了优化性能,有时候会改变程序中语句的先后顺序。

 

第二讲:Java内存模型


1.可以通过禁用缓存和编译优化来解决可见性和有序性问题
2.Happens-Before 约束了编译器的优化行为,虽允许编译器优化,但是要求编译器优化后一定遵守 Happens-Before 规则。Happens-Before 的语义是一种因果关系。在现实世界里,如果A事件是导致B 事件的起因,那么A事件一定是先于(Happens-Before)B事件发生的,这个就是 Happens-Before 语义的现实理解。
3.Java内存模型
a、程序的顺序性
b、volatile
c、传递性 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C
d、管程中锁的规则 指对一个锁的解锁 Happens-Before 于后续对这个锁的加锁
e、线程start()规则 它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。
f、线程join()规则 这条是关于线程等待的。它是指主线程A等待子线程B完成(主线程A通过调用子线程B的join()方法实现),当子线程B完成后(主线程A中join()方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作
4.管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现
5.final 修饰变量时,初衷是告诉编译器:这个变量生而不变,可以可劲儿优化

 

第三讲:互斥锁


1.互斥 同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥
2.Java语言提供的锁技术:synchronized,当修饰静态方法的时候,锁定的是当前类的Class对象,当修饰非静态方法的时候,锁定的是当前实例对象this
3.用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁还有个名字,叫细粒度锁。使用细粒度锁可以提高并行度,是性能优化的一个重要手段。使用细粒度锁是有代价的,这个代价就是可能会导致死锁。
4.死锁发生的条件如下,只要破坏或者避免这些就可以避免死锁
a、互斥,共享资源 X 和 Y 只能被一个线程占用
b、占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X
c、不可抢占,其他线程不能强行抢占线程 T1 占有的资源
d、循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待
5.等待-通知 synchronized、wait、notify、notifyAll
wait和sleep区别
wait释放资源,sleep不释放资源
wait需要被唤醒,sleep不需要
wait需要获取到监视器,否则抛异常,sleep不需要
wait是object顶级父类的方法,sleep则是Thread的方法

 

第四讲:安全性、活跃性、性能问题

1.安全性问题:线程安全其实本质上就是正确性,而正确性的含义就是程序按照我们期望的执行,不要让我们感到意外。理论上线程安全的程序,就要避免出现原子性问题、可见性问题和有序性问题。存在共享数据并且该数据会发生变化,通俗地讲就是有多个线程会同时读写同一数据。
竞态条件(Race Condition) 所谓竞态条件,指的是程序的执行结果依赖线程执行的顺序。
2.活跃性问题:所谓活跃性问题,指的是某个操作无法执行下去。我们常见的“死锁”就是一种典型的活跃性问题,当然除了死锁外,还有两种情况,分别是“活锁”和“饥饿”。公平锁
3.性能问题:性能方面的度量指标有很多,我觉得有三个指标非常重要,就是:吞吐量、延迟和并发量。

 

第五讲:管程


1.所谓管程,指的是管理共享变量以及对共享变量的操作过程,让他们支持并发。翻译为 Java 领域的语言,就是管理类的成员变量和成员方法,让这个类是线程安全的。

 

第六讲:Java线程生命周期


1.通用线程周期:初始状态、可运行状态、运行状态、休眠状态和终止状态。
2.Java线程生命周期:NEW(初始化状态)、RUNNABLE(可运行 / 运行状态)、BLOCKED(阻塞状态)、WAITING(无时限等待)、TIMED_WAITING(有时限等待)、TERMINATED(终止状态)
3.可以通过 jstack 命令或者Java VisualVM这个可视化工具将 JVM 所有的线程栈信息导出来,完整的线程栈信息不仅包括线程的当前状态、调用栈,还包括了锁的信息

 

第七讲:如何提升性能


1.性能指标:最重要的,延迟,吞吐量
2.优化方法:一个是优化算法,一个是提升硬件性能利用率(i/o利用率和CPU利用率)
3.创建线程数:CPU密集型,线程数=CPU核数+1;i/o密集型,线程数=2*CPU核数+1
4.局部变量:局部变量就是放到了调用栈里,栈帧和方法是同生共死的,每个线程都有自己独立的调用栈

 

第八讲:面向对象写并发程序


1.封装共享变量:将共享变量作为对象属性封装在内部,对所有公共方法制定并发访问策略
2.识别共享变量间的约束条件
3.制定并发访问策略
注意:
1.优先使用成熟的工具类:Java SDK 并发包里提供了丰富的工具类,基本上能满足你日常的需要,建议你熟悉它们,用好它们,而不是自己再“发明轮子”,毕竟并发工具类不是随随便便就能发明成功的。
2.迫不得已时才使用低级的同步原语:低级的同步原语主要指的是 synchronized、Lock、Semaphore 等,这些虽然感觉简单,但实际上并没那么简单,一定要小心使用。
3.避免过早优化:安全第一,并发程序首先要保证安全,出现性能瓶颈后再优化。在设计期和开发期,很多人经常会情不自禁地预估性能的瓶颈,并对此实施优化,但残酷的现实却是:性能瓶颈不是你想预估就能预估的。

 

posted @   shog808  阅读(44)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示