为什么开发中需要并发编程
加快响应用户的时间
使代码模块化,异步化,简单化
充分利用CPU的资源
基础概念
进程
- 运行这些应用程序,指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备,从这种角度来说,进程就是用来加载指令、管理内存、管理 IO的。
- 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。进程就可以视为程序的一个实例。
- 站在操作系统的角度,进程是程序运行资源分配(以内存为主)的最小单位。
线程
怎么让有限的CPU运行这么多程序 → CPU调度。线程则是CPU调度的最小单位。它是比进程更小的、能独立运行的基本单位。
同属一个进程的其他的线程共享进程所拥有的全部资源。
Java中不管任何程序都必须启动一个main函数的主线程
进程间通信有几种方式
- 管道(Pipe):管道可在具有亲缘关系的进程间提供单向的数据通信。
- 命名管道(Named pipe):命名管道类似于匿名管道,但是它可以实现无亲缘关系进程间的通信。
- 消息队列(Message queue):消息队列允许进程通过队列来交换数据或信号。
- 共享内存(Shared memory):共享内存允许多个进程共享一个给定的内存区段,从而可以交换数据。
- 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。
- 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
- 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。
信号是软中断,由内核触发;信号量是一个计数器,由进程操作。
CPU核心数和线程数的关系
同一时刻,一个CPU核心只能运行一个线程
Intel引入超线程技术后,产生了逻辑处理器的概念,逻辑处理器比内核数大。
并发编程下的性能优化往往和CPU核心数密切相关。
//获取逻辑处理器数
System.out.println("CPU cores: " + Runtime.getRuntime().availableProcessors());
上下文切换
上下文切换可以更详细地描述为内核(即操作系统的核心)对CPU上的进程(包括线程)执行以下活动:
- 暂停一个进程的处理,并将该进程的CPU状态(即上下文)存储在内存中的某个地方
- 从内存中获取下一个进程的上下文,并在CPU的寄存器中恢复它
- 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。
引发上下文切换的原因一般包括:线程、进程切换、系统调用等等。
就CPU时间而言,一次上下文切换大概需要5000~20000个时钟周期,相对一个简单指令几个乃至十几个左右的执行时钟周期,可以看出这个成本的巨大。
并行和并发
并发Concurrent 能够交替执行不同的任务
并行Parallel 能够同时执行不同的任务
认识Java里的线程
main线程,用户程序入口
ThreadMXBean thread = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = thread.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() +"]" +threadInfo.getThreadName());
}
/****
[1]main
[2]Reference Handler
[3]Finalizer
[4]Signal Dispatcher
[5]Attach Listener
[21]Common-Cleaner
[22]Monitor Ctrl-Break
[23]Notification Thread
****/
线程的启动与中止
在java中启动线程的方式有两种
- X extends Thread;,然后X.start
- X implements Runnable;然后交给Thread运行
线程的中止
-
线程自然终止
- 要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
-
stop
- 暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。
- 因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
-
中断
- **interrupt()**对线程进行中断
- **isInterrupted()**来进行判断是否被中断
- **Thread.interrupted()**来进行判断当前线程是否被中断,不过会同时将中断标识位改写为false。
- 如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常
不建议自定义一个取消标志位来中止线程的运行
- 一般的阻塞方法,如sleep等本身就支持中断的检查
- 检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
注意:处于死锁状态的线程无法被中断
深入理解run()和start()
start之后与线程一一对应,重复调用会抛出异常。
run只是一个入口方法,可以重复调用。