目录

背景

原文第1-3章结构

第1章 并发编程的挑战

并发编程好吗?

如何减少上下文切换(以下取自原书P12)


观前提醒:十分建议阅读原书,本菜狗还在入行中,对很多知识的理解不及原作者之万一,再深入的理解可能也难以复刻原文的思想,还请见谅(其实本来就是写给自己乐呵下的,嘻嘻)。

背景

最近遇到了需要打印持锁信息的需求,就想顺便了解下锁具体是什么。听说这本书不错,就整了本电子版过来看了看(目前只看了前3章)。个人觉得书写得好棒啊,很涨知识。但知识量也还挺大的,不自己梳理一遍可能不久就忘了,白学了。所以想着还是自己梳理一遍吧,教别人自己学的也快。

并且原先和朋友说,要不我整个号,发发东西?那就把这篇文章当做是第一篇(也非常有可能,啊不,极有可能是最后一篇)吧。

原文第1-3章结构

Java 并发编程实践 pdf java并发编程的艺术电子书_多线程

第1章 并发编程的挑战

并发编程好吗?

并发编程好啊,线程多了,同时处理的事情就多了,处理速度就上去了。但事实并不总是这样。CPU是分时间片执行指令的,并发会导致频繁地切换上下文[1],从而使真正运行指令的时间减少,在某些情况下甚至不如单线程。

[1] 上下文,即context,其实理解为执行环境会更好些(对我个人而言更好理解)。每次切换上下文都需要Linux由 用户态 -> 内核态 -> 用户态

“CPU分片执行指令,时间片用完后切换上下文,多线程同时处理的事情多”这段话你品,你细品,感觉是有点奇怪的。

为什么多线程会好于单线程呢?既然CPU是按时间片执行指令的,就算时间片不是定长的[2],可是如果单位时间内切分出的时间片数量相同,假如在上述时间片内任意一个线程都没有完成任务,从而都要进行上下文切换,那么不管单线程多线程它们的上下文切换的总次数、总耗时不应该是一样的吗,所以完成任务的总时间不也应该是一样的吗?

[2] 为了获得较快的响应速度,交互性强的进程(即趋向于IO消耗型)被分配到的时间片要长于交互性弱的(趋向于处理器消耗型)进程

这么想确实有迷惑性,但是:

  1. 每个CPU确实是按时间片执行指令的,但是单线程只能用1个CPU,而多线程可以用多个CPU
  2. 计算机又不是只有你这一个任务会产生线程,不是只有你创建出线程需要资源,你们所有线程分一个盘子,那么你创建出的线程多了,你拿到的资源总数是会变多的
  3. 第1、2两点说明多线程可以使任务变快,而这第3点则与1、2相反。单位时间内切分出的时间片数量并不相同,如果线程增多,那么单位时间内切分出的时间片数量也会增大,切换上下文产生的损失就越大,具体如下图所示:

Java 并发编程实践 pdf java并发编程的艺术电子书_Java 并发编程实践 pdf_02

vmstat[3]特别需要关注的四列内容:

  • cs (context switch) 每秒上下文切换的次数
  • in (interrupt) 每秒中断的次数
  • r (Running or Runnable) 就绪队列的长度,也就是正在运行和等待CPU的进程数
  • b (Blocked) 处于不可中断睡眠状态的进程数

在这里我们只需要关注cs就好了,这里是创建了太多线程,每秒切换1k次,正常情况下是不会达到这个值的。

如果1、2干得过3,那么多线程;如果干不过,单线程更好。综上,多线程在某些情况下是会比单线程要快的。

[3] 分析上下文切换的指令:
vmstat 系统总体的上下文切换情况
pidstat –w 每个进程的详细情况
Lmbench3 可以测量上下文切换的时长

如何减少上下文切换(以下取自原书P12)

  • 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
  • CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
  • 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
  • 协程(我完全不会):在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。