背景
- 计算机由CPU、存储器、输入输出设备组成。
- CPU内部又包括了控制器、运算器、寄存器(存储器)
- 其中进程是计算机资源分配的最小单位,线程是程序执行的最小单位(CPU资源调度的最小单位) 。
- CPU通过给每个线程分配CPU时间片来达到并发执行的效果。
- 每个线程得到的时间片都很短,一般是几十毫秒(ms)
- CPU通过不停地切换线程,让我们感觉多个线程在同时执行
并发编程带来的挑战
- 当CPU切换线程时,需要保存上一个线程任务的状态,加载当前线程任务的状态,即上下文切换。
- 上下文切换的过程会消耗少量CPU资源的,非常频繁的切换反而会浪费CPU资源
- 减少上下文切换的方法
- 无锁并发编程:避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据
- CAS算法:Java的Atomic包使用CAS算法来更新数据,而不需要加锁
- 使用最少线程:避免创建多余的线程,避免大量线程都处于等待状态的情况发生。
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
- 死锁是指多个进程在运行中因争夺资源造成的一种僵局,此时若无外力作用,它们都将无法再向前推进
- 产生死锁的4个必要条件
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
- 避免死锁的几个方法:
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
- 资源限制:在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源(cpu、磁盘、带宽)
- 解决资源限制的问题
- 对于硬件资源限制,可以考虑使用集群并行执行程序
- 软件资源限制,可以考虑使用资源池将资源复用
Java中的几种IO模型
- Linux的虚拟地址空间也为0~4G。
- 将最高的1G字节(虚拟地址 0xC0000000~0xFFFFFFFF),供内核使用,称为内核空间。
- 而将较低的3G字节(虚拟地址 0x00000000~0xBFFFFFFF),供各个进程使用,称为用户空间
- 内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。
- 内核空间和用户空间之间一般通过系统调用进行通讯
- 进程在执行用户自己的代码时,一般称其处于用户态,此时处理器特权级最低(3级)
- 当一个进程执行系统调用而陷入内核代码中执行时,一般称进程处于内核态(0级)
- CPU运行于进程上下文时,内核代表进程运行于内核空间
- CPU运行于中断上下文时,内核代表硬件运行于内核空间
- 进程上下文是用户进程传递给内核的一些参数以及内核要保存的寄存器值和当时的环境
- 中断上下文是硬件传递过来的一些参数和内核需要保存的进程环境
- 用户态和内核态之间进行切换时同样耗费CPU资源,尤其是IO处理时会出现数据的重复拷贝
- 标准IO
- 标准IO读写会发生4次数据拷贝(DMA拷贝和内存拷贝),4次上下文切换(用户态/内核态)
- 读:数据从磁盘读取到内核缓存,然后从缓存读取数据
- 写:数据从用户地址空间复制到内核地址空间的缓存中,系统会自己决定何时将缓存写入磁盘
- 直接IO
- 读:数据从磁盘读取到用户程序自己的缓存中,然后从缓存读取数据
- 写:数据写入用户程序自己的内存中缓存,然后由用户程序写入磁盘,一般都是数据库管理系统
- 同步IO:写入数据时,一直阻塞到当数据从缓存刷入磁盘时成功后
- 异步IO:访问数据的线程发起请求后继续处理其他的事情,当数据返回后再继续处理
- 内存映射:关联磁盘的空间地址与内存,将访问内存转为直接访问文件的某一部分。省去了复制的过程
- 零拷贝:
- 使用native方法transferTo0()实现,直接在内核空间完成文件读取并转到磁盘,使用sendfile系统调用
- 效率高但无法操作文件内容,2次数据拷贝(0次CPU拷贝),2次上下文切换
- NIO:零拷贝技术;直接内存映射:MappedByteBuffer将文件映射到堆外内存中,有内存泄露的可能
Java并发编程模型
-
常用同步原语
- synchronize
- volatile
- 通过内存屏障指令来禁止特定类型的处理器重排序
- final
-
原子操作
-
操作系统线程状态:新建,运行中,就绪,等待,结束
-
java线程状态:新建,可运行(运行中+就绪),阻塞+等待+超时等待(等待),结束
-
线程之间的通信:一般有共享内存和消息传递两种方式。Java的并发采用的是共享内存模型。
-
线程之间的同步:同步是指程序中用于控制不同线程间操作发生相对顺序的机制
-
JMM了决定一个线程对共享变量的写入何时对另一个线程可见。(主内存+)
-
线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。本地内存涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化
-
内存可见性:在JMM中(JSR-133),一个操作执行的结果要对另一个操作可见时,这两个操作间就是存在happens-before关系。
-
顺序一致性,所有操作必须按照程序的顺序来执行,且每个操作都必须原子执行且立刻对所有线程可见。
未完待续!