不是启动越多的线程,程序就能最大限度地并发执行。并发编程通常会受到上下文切换、死锁、硬件和软件资源等问题的影响。
上下文切换
- 单核cpu通过为每个线程分配cpu时间片来实现多线程,时间片时长通常为几十毫秒。
- 上下文切换会影响到多线程的执行速度。
问题: 并发执行的代码是不是肯定比串行执行的快?
回答: 不一定,因为多线程的创建和切换上下文也有开销,所以在循环次数较少时,耗时效果并不理想。通常需要百万级别以上,多线程的优势才能被体现出来。
上下文切换的开销会影响到整体的耗时,所以因尽量减少上下文切换,可采取以下策略:
- 无锁并发编程:多线程中减少使用锁的次数,从而减少竞争锁时带来上下文切换的次数,如将数据的ID按照Hash算法取模分段,不同线程处理不同段的数据;
- CAS算法:利用该算法更新数据,可不需加锁
- 使用最少线程:适量创建线程个数,减少不必要线程产生的等待时间
- 协程:在单线程中实现多任务的调度,并在单线程中维持多个任务见的切换,这样就没有多线程的切换。
死锁
死锁的概念
当两个或两个以上的进程在执行过程中,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,就称为死锁。
避免死锁的方法
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
资源限制的挑战
- 资源限制概念
指在并发编程中,程序的执行速度受到计算机硬件或软件资源影响的情况。硬件资源限制有:带宽、硬盘读写、cpu处理速度等;软件资源限制有:数据库链接数和socket连接数等。 - 在资源限制情况下,原本将串行代码并发执行以此来加快执行时间,但这种情况下由于资源限制,导致代码仍只能串行执行,但是并发中加入了上下文切换和调度的时间,从而导致整体运行时间不减反增。
- 处理资源限制方法
- 硬件资源限制:由于是计算机资源受限,所以可以采用增加计算机个数的方法,如用集群并发执行程序。
- 软件资源限制:使用资源池复用资源,不使用资源池而频繁地创建销毁连接,会极大地浪费系统资源,增加响应耗时,影响系统性能。
问题总结
协程的概念?
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。没有生命周期。
JDK提供了哪些并发容器和工具类可以用来解决并发问题?
java.util.concurrent及其子包,具体包含以下几个方面:
- 提供了几个非常有用的并发工具类,包括CountDownLatch、CyclicBarrier、Semaphore等,可以实现更加丰富的多线程操作。比如利用Semaphore作为资源控制器,限制同时进行工作的线程数量。
- 各种线程安全的容器,比如最常见的ConcurrentHashMap、有序的ConcurrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组CopyOnWriteArrayList等。
- 各种并发队列实现,如各种BlockingQueue实现,比较典型的ArrayBlockingQueue、SynchorousQueue或针对特定场景的PriorityBlockingQueue等。
- 强大的Executor框架,可以创建各种不同类型的线程池,调度任务运行等。绝大部分情况下,不再需要自己从头实现线程池和任务调度器。
思考
join 是什么
join()方法是Thread类中的一个方法,方法的定义为等待该线程终止,底层通过wait()方法实现。
更多内容可参考:What does this thread join code mean?
vmstat 怎么用,参数怎么用,输出内容都指的是什么
vmstat: 检测系统CPU/内存/磁盘输入输出状态等。1
2
3
4
5
6
7
8
9
10
11
12vmstat [-a] [延迟 [总计检测次数]] <==CPU/内存等信息
vmstat [-fs] <==内存相关
vmstat [-S 单位] <==设置显示数据的单位
vmstat [-d] <==与磁盘相关
vmstat [-p 分区] <==与磁盘相关
参数:
-a : 使用 inactive/active(活跃与否)替代 buffer/cache的内存输出信息;
-f : 开机到目前为止系统复制(fork)的进程数;
-s : 将一些事件(开机到目前为止)导致的内存变化情况列表说明
-S : 后面可以接单位,让显示的数据有单位。例如 K/M 取代 bytes 的容量;
-d : 列出磁盘的读写总量统计表
-p : 后面列出分区,可显示该分区的读写总量统计表
示例: vmstat 1 3:统计目前主机CPU状态,每秒一次,共计三次!各字段说明如下:
内存栏位 (procs) 的项目分别为:
r :等待运行中的程序数量;
b:不可被唤醒的程序数量。这两个项目越多,代表系统越忙碌 (因为系统太忙,所以很多程序就无法被运行或一直在等待而无法被唤醒之故)。内存栏位 (memory) 项目分别为:
swpd:虚拟内存被使用的容量;
free:未被使用的内存容量;
buff:用于缓冲内存;
cache:用于高速缓存。 这部份则与 free 是相同的。内存置换空间 (swap) 的项目分别为:
si:由磁碟中将程序取出的量;
so:由于内存不足而将没用到的程序写入到磁碟的 swap 的容量。 如果 si/so 的数值太大,表示内存内的数据常常得在磁碟与主内存之间传来传去,系统效能会很差!磁碟读写 (io) 的项目分别为:
bi:由磁碟写入的区块数量;
bo:写入到磁碟去的区块数量。如果这部份的值越高,代表系统的 I/O 非常忙碌!系统 (system) 的项目分别为:
in:每秒被中断的程序次数;
cs:每秒钟进行的事件切换次数;这两个数值越大,代表系统与周边设备的沟通非常频繁! 这些周边设备当然包括磁碟、网络卡、时间钟等。CPU 的项目分别为:
us:非核心层的 CPU 使用状态;
sy:核心层所使用的 CPU 状态;
id:闲置的状态; wa:等待 I/O 所耗费的 CPU 状态;
st:被虚拟机器 (virtual machine) 所盗用的 CPU 使用状态 (2.6.11 以后才支持)。
cas 是什么
CAS(比较与交换,Compare and swap) 是一种有名的无锁算法,维护三个变量值,一个是内存值V,一个是期望的旧的值A,一个是要更新的值B。更新一个变量的时候,只有当预期值A与内存V中的值相等的时候,才会执行更新操作,把内存V的值改为B。
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
从思想上来说,synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以只能在一个线程获取到锁后,其他线程被挂起等待;CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。
awk 是什么,怎么用
awk 也是一个非常好用的数据处理工具,awk倾向于一行当中分成数个『栏位』来处理。而默认的『栏位的分隔符号为 “空白键” 或 “[tab]键” 』!awk 通常运行的模式是这样的:1
2awk '条件类型1{动作1} 条件类型2{动作2} ...' filename
awk后面接两个单引号并加上大括号 {} 来配置想要对数据进行的处理动作。
示例:e.g. 输出poem中第2行至第6行的第2列与第4列内容以及第1列乘10结果,中间以#符分割(提示:NR为当前处理行的行号)
1 | cat poem |
更多内容参见:Linux awk 命令
linux 管道是什么鬼
管道是Linux中很重要的一种通信方式,是把一个程序的输出直接连接到另一个程序的输入,常说的管道多是指无名管道,无名管道只能用于具有亲缘关系的进程之间,这是它与有名管道的最大区别。
有名管道叫named pipe或者FIFO(先进先出),可以用函数mkfifo()创建。
jstack 命令怎么用
jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,命令格式如下:1
2
3
4
5
6
7
8
9
10jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
参数说明:
pid: java应用程序的进程号,一般可以通过jps来获得;
executable:产生core dump的java可执行程序;
core:打印出的core文件;
remote-hostname-or-ip:远程debug服务器的名称或IP;
server-id: 唯一id,假如一台主机上多个远程debug服务;
官方文档参见:jstack
- TIMED_WAITING 是什么意思
一个线程在一个特定的等待时间内等待另一个线程完成一个动作会在这个状态