读书笔记

一:《Java编发编程实战》

简介部分

介绍了并发编程的起因:嵌入式单片机处理性能差(无操作系统的话)。单核处理器的资源利用率低,进程本质上还是串行的,串行编程对异步事件的响应不理想。所以有了线程的出现。线程共享进程的公共资源如内存句柄和文件句柄。私有各自的程序计数器,栈和局部变量等。线程将大部分异步工作流转换为串行工作流。
线程的风险:
安全性问题(访问非原子变量),
活跃性问题即某件正确的事情最终会发生,而某个操作无法继续执行下去就发生了活跃性问题(饥饿,死锁,活锁)。
性能问题:吞吐量,响应速度,可伸缩性等如频繁的上下文切换会影响系统的开销。
最后说明了线程无处不在,我们应该关注使用多线程的上下文和关联路径都是线程安全的,确保整体程序的运行正确。

第二部分

介绍了线程安全性的概念。说明导致线程不安全的根本原因是“基于一个可能失效的观测结果来做判断或执行某个操作”。解决方案是“将对象的状态封装到类中,设计这个类是线程安全的。那么访问这些状态变量就会是线程安全的了”。
工具: 原子性变量和加锁机制(如synchronized lock,重入锁(内置锁是可重入的锁))。

从不需要线程状态到添加一个线程状态可以使用原子变量。但是从一个线程状态到多个线程状态的安全性不能使用多个原子变量。需要使用加锁机制。

但是要注意synchronize 锁过于庞大可能会使原有的系统性能不能满足。所以要尽量细分同步代码块的范围,使每一块儿代码块保证同步效果而整体系统的性能也满足要求。(如servlet的缓存机制中判断是否是之前出现的数据,和更新历史数据是放在两个同步代码块中)

并发容器部分

这部分在书中是三—五章。主要讲了java提供的并发容器以及使用注意事项。
当对一段foreach代码并行处理(改删数据)时,容易引发访问错误。原因是迭代器iterator会遍历容器数据,而在遍历过程中数据有可能已经被修改。所以要使用线程安全的表达:Iterator.remove().而不是直接删除。
同时对可能引发并发问题的代码加Synchronized会大大降低性能,失去并发的意义。可以考虑对更细粒度的代码加锁。
如并发容器concurrentHashMap()使用分段锁实现并发访问,并发读写,并发读,一定数量的并发写。
还提供了阻塞队列blockingqueue等。

并发实战之任务执行部分

这部分内容对应书中第六章。主要讲了实际编程中如何实现多线程。服务器响应最简单的是单线程阻塞响应串行执行,这种响应方式会因为服务器在一个任务中调用外部IO而耗时高,造成卡顿。
如果对客户端的每一个响应都创建一个线程去执行,会因为请求过多而占用系统资源,GC也会有很大的麻烦。
最优的方式是使用任务抽象。而不是线程抽象。Executor提供了异步执行框架,基于生产者-消费者模式。提交任务的相当于生产者,执行任务的相当于消费者。
与线程池的关系?(通过调用Exectors中的静态工厂方法之一创建一个线程池)
要使用Excetor,需要将任务抽象为一个runnable.大多数服务器应用程序存在一个明显的任务边界:单个客户的请求。而在很多桌面应用程序中,单个客户请求仍可能存在可挖掘并发的点。如前端渲染将文字和图片分开渲染。
两种方案实现渲染桌面:向Executor提交一组计算任务。保留与每个任务关联的future,然后反复使用get方法,通过轮询判断任务是否完成。此方案可行但繁琐,对于性能的提升仅在多个同构任务提交时可行。
或者方案二:使用完成服务(CompletionService)。这种方案粒度更细。使用了阻塞队列将每个图片的操作放在一个线程中提交给exceutor.使得响应更快,并减少下载所有图像的总时间。

二:《深入理解计算机系统》

导论

主要介绍了计算机系统一个简单程序的运行过程。引出了计算机系统的多线程概念:在shell中输入待编译的文件后,系统创建新的线程编译该文件,编译执行完成后返回shell线程输出结果:hello world。
三种抽象是多线程的基础:文件是对IO设备的抽象,虚拟内存是对IO设备和主存的抽象。进程是对处理器,主存和IO设备的抽象。更大的:虚拟机是对操作系统和进程的抽象。虚拟内存的地址按照倒三角的方式从下往上分别是程序代码数据、运行时堆、共享库的内存映射区域、用户栈空间、以及用户代码不可见的内核虚拟内存:这个空间的内容不允许应用程序读写或者直接调用内核代码定义的函数,

一、基本数据格式和指令执行原理

基本数据格式包括和Java类似的8大基本类型:char、int、long、double、float等在x86中的内存表示,数据的范围。
对于汇编指令有了更深的理解。编译器可以对代码进行优化(尽管这种优化并不一定能提高性能)。以及机器、数据类型和指令集如基本运算、逻辑语句(while-done,for)的底层汇编代码。本章重点是程序如何将数据存在不同的内存区域。比如运行时栈中?某个动态分配的数据结构中?还是全局程序数据的一部分?
编译c++与编译c非常相似。c++的早期实现就是简单执行了从c++到c的源到源的转换,并对结果运行c编译器,产生目标代码:c++的对象用c的结构(Struct)表示,c++的方法是用指向方法的代码的指针来表示。Java则实现方式完全不同。Java的目标代码是一种特殊的二进制表示(Java字节码)这种代码可以堪称虚拟机的机器级程序,虚拟机并不是直接用硬件实现,而是用软件解释器处理字节代码。

二、程序优化

本章主要介绍了性能优化的几个方法。引入标准CPE(每元素的周期数)表示不同程序的性能差异
优化一:在for循环中不判断字符串的长度。而是将长度的计算放在for循环之前,将算法的复杂度由平方级降为线性级
优化二:将Java对向量边界的检查去掉可以达到优化的目的,换成数组首地址并累加索引。需要注意的是这种消除边界检查的预测仅对整数运算有作用,其他的情况“受限于各自合并操作的延迟”即边界检查的运算预测和自身运算的合并操作并行执行,由于合并操作相对更费时一些,显得是否执行边界检查起不到影响。
优化三:涉及到指针的使用,指针会增加内存引用,换做累加器直接累加,最后再通过指针赋累加和可以减少不必要的 内存调用
优化四:循环展开可以增加并行运算。是提高CPE的有效方式。将一个循环的累加分奇偶分别累加,然后奇偶结果求和得到最终结果会提高CPE。即2*2.

三、存储器结构

本章主要讲述计算机存储器的分类。按照存储器分级可以构造存储器金字塔。SRAM是高速缓存,速度仅次于寄存器。DRAM是主存,速度慢于SRAM。存储器的分层分级保证CPU需要的数据可以最快速获得。各个级别的存储去都是下一级存储器的缓存,能够在本级别存储器找到数据称为命中。应该尽量提高空间和时间局部性使命中率更高。具体来说:循环体尽量小可以将获取指令的空间局部性提高。数据按照存储结构访问可以提高时间局部性比如二维数组按行访问顺序列值。
通过本章,我对磁盘认识全面加深,比在博客获取的知识更全面可信。磁盘可以有多个盘面,每个盘也有上下面。每一面有多个磁道,磁道划分为大小相等的扇区,并且每个磁道的扇区数目相等。所以内小外大,呈现扇形。磁头旋转到对应扇区位置时读取某个盘面的数据时有上下两个感应体读取上下面的数据。

四、系统中程序链接的过程

在Linux系统中执行.c文件的过程包括:
源代码经过预处理器生成中间文件(main.i)。
中间文件经过编译器翻译为ASCII汇编语言文件main.s
汇编器将main文件翻译成可重定位目标文件main.o、
多个可重定位目标文件以及一些必要的系统目标文件通过链接器生成可执行文件。
可执行文件由操作系统的加载器函数调用,将可执行文件中的代码和数据复制到内存,并控制转移到这个程序的开头执行。

可重定位目标文件详细描述了代码中变量的类型和相互关系。由来。以及符号表。还有机器指令的描述。

对于可重定位目标文件的链接,重要的一点就是链接器要解释符号表的含义。对于局部符号引用很容易关联程序。但对于全局符号引用要分多种情况讨论。

除了对一组可重定位目标文件链接外,还可以将所有相关的目标文件打包成一个单独的静态库,作为链接器的输入。保证在链接器构造一个输出的可执行文件时,只复制静态库里被应用程序引用的目标文件。这样保证不需要很大的重复开发复杂度以及对内存友好。

针对静态库在运行时需要为每一个程序提供可能相同的函数如printf和scanf。以及标准函数更新后库更新繁琐的问题。动态链接器可以在静态链接器将部分链接的可执行文件映射到内存后,链接和加载共享目标文件(共享库),该库可以被多个运行进程共享,节省了内存开销。

五,异常处理机制

异常控制流是计算机系统提供并发的基本机制。发生在计算机系统的各个层次。
四种不同类型的异常:中断,故障,终止和陷阱。
中断:当外部I/O设备设置处理器芯片上的中断管脚时,中断会异步地发生。中断会返回下一条指令。
故障和终止:一条指令的执行可能导致故障和终止同步发生,故障处理程序会启动故障指令如果故障被处理那么重新执行当前指令,或无法处理故障由终止处理程序使程序终止
陷阱:用来实现向应用提供到操作系统代码的受控访问入口点的系统函数调用。陷阱是有意的异常,在用户程序调用read等向内核请求的服务时会将执行相应的陷阱指令访问内核程序。陷阱返回下一条指令

深入理解并发并行:并发是一种逻辑控制流的方式。它指的是两个进程在执行时间线上有重叠,不限定是否为同一处理器。对于一片处理器上的并发流,其本质是上下文切换使程序交替执行。针对不同处理器上同时执行的多个进程的现象称为并行(也叫并发)。并行流是并发流的真子集。重要的,并发概念的建立允许计算机内核以任意的方式交替执行他们逻辑控制流的指令(方法就是进程在各自的上下文中被内核陷入或中断,然后执行另一段上下文),从而有了进程的重要抽象即每个进程都独占的使用处理器和主存

内核通过异常控制流提供进程的重要抽象:1,每个应用程序好像在独占地使用处理器。2,每个应用程序好像在独占的使用主存。
操作系统除了用异常来支持进程上下文切换。还有一种更高层的软件形式的异常——Linux信号。它允许进程和内核中断其他进程。具体过程分两步1.发送信号:内核更新目的进程上下文的某个状态,传送一个信号给目的进程。
2.接收信号。目的进程被内核强迫以某种方式对信号的发送做出反应。控制会传递到信号处理程序,待信号处理程序运行完成后返回到进程接收信号的下一条指令。

六,虚拟内存

虚拟内存是对主存的一个抽象,是对主存的高级管理。处理器读取主存数据通过使用一种叫做虚拟寻址的间接形式引用主存。
概念:虚拟内存是在磁盘上的数组序列。被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组,每个字节都有唯一的虚拟地址作为到数组的索引。VM系统将磁盘虚拟内存分割成大小固定的虚拟页方便处理。虚拟页三状态:未被分配,已缓存的,未缓存的。
页表是存放在物理内存中的数据结构,页表将虚拟页映射到物理页。操作系统为每个进程提供一个独立的页表。也就是有一个独立的虚拟地址空间。这种独立地址空间允许每个进程的内存映像使用相同的基本格式,不管代码和数据实际存放在物理内存中的位置。也就是在链接过程中,进程在操作系统中的内存模型都是从虚拟地址0x400000开始。数据段跟在代码段之后

读取过程:处理器读取一个虚拟地址。
虚拟地址的功能:
1 虚拟内存在主存中自动缓存最近使用的存放在磁盘上的虚拟地址空间的内容。
2.虚拟内存简化了内存管理,通过分配虚拟内存而非物理地址内存。这进而简化了链接、进程间共享数据、进程的内存分配以及程序加载。
3 虚拟内存在每条页表中加入保护位简化了内存保护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值