数据结构单链表的实现实现的堆栈(求完整代码,谢谢

摘要: 随着计算机硬件技术的发展,如今我们已经迈入了多核CPU时代.然而,作为软件核心的数据结构仍然是按照单核CPU和顺序型准则来设计的.在基于共享内存的多核时代,大量并发运行的线程会交替地修改数据,产生不可预期的结果,因而我们面临着严峻挑战.针对基于共享内存多核时代数据结构的相关研究进行综述.首先,对比了并发与并行的区别,归纳了基于演进条件(progress condition)的多核数据结构分类,对近年来学术界对各种类型并发数据结构的研究进行综述.在此基础上,剖析了并发数据结构设计和实现的关键技术,并从并发数据结构的开发流程、正确性验证等方面进行了归纳阐述.最后,基于这些讨论,对多核架构下并发数据结构未来的研究趋势和应用前景进行了展望.

随着计算机技术的发展,研发人员意识到通过不断增加主频来提升CPU性能的时代已经结束,近年来,CPU架构更加注重低功耗和多核心.芯片设计工程师将两个或多个内核封装到单一处理器中,片上多核处理器已经成为处理器发展的趋势.多核带来的性能提升及其在商业上的成功使得多核心架构从最初的2核、4核,跨越到8核、16核,不久的将来,普通PC机或许将迎来超过100核的处理器.那么,这波多核运动要发展到什么程度呢?没有人能给出一个明确的答案.可以肯定的是,我们已经迈入了一个多核时代,这已成为计算机设计和应用中不可回避的问题[].

多核时代给人们带来了机遇,同时也带来了巨大的挑战.在一个单独封装的处理器内拥有多个内核,它们共享同一块内存,并发执行多个线程时通过共享内存中的数据结构来进行通信和同步[].随着内核数量的增多,程序对共享资源访问的冲突也在加剧,这成为一个亟待解决的问题[, ].虽然在过去的几十年中,研究者们已经在多核处理器的机器上运行软件程序,然而这些软件程序通常是数值计算、大规模矩阵运算等科学研究性质的并行程序,或者是未针对多核处理器优化的顺序型程序.伴随着商用多核(multicore)系统成本的降低,未来个人电脑、平板电脑、手机等设备势必将具备更多核心,我们越来越需要对原有的顺序型程序进行改造和优化,使其能够更充分地利用多核资源[, ].目前面临的瓶颈是多核软件的发展没有跟上硬件的发展.

数据结构是软件设计的核心,对整个软件的性能有着重要影响.往往在数据结构上的一些细微改动就能够很大程度地提升整个软件的运行效率.然而,目前大部分程序员、科研工作者对多核时代数据结构并发编程的理解还不够深刻,只有少部分人具备设计并发程序的能力[].对于大多数人而言,设计多核并发数据结构的难点在于:在一个进程内大量并发运行的线程会交错地执行指令,从而产生不可预期的结果.这就要求我们对多核时代的数据结构有新的认识,用新的方法和工具集来开展研究和设计[].

总体而言,多核架构下基于共享内存的并发数据结构仍然有很多问题亟待解决.本文针对多核时代数据结构的相关研究进行综述.剖析了国内外各类并发数据结构的研究现状,总结了并发数据结构设计和实现的关键技术.最后,基于这些讨论,对多核架构下并发数据结构未来的研究趋势和应用前景进行了展望.

本文在第1节分析数据结构并发的重要性及其与并行的区别.第2节归纳提出基于演进条件(progress condition)的并发数据结构分类.第3节对近年来学术界对各类并发数据结构(如:堆栈、链表、哈希、树型结构等)的研究现状进行综述.在此基础上,在第4节~第6节总结并发数据结构的关键技术、开发流程和理论正确性验证方法.最后在第7节对多核架构下并发数据结构未来的研究趋势和应用前景进行展望.

1 数据结构并发的重要性及其与并行的区别 1.1 并发与并行的区别

多核系统在一段时间片段内,运行的线程会交错地执行指令.因此,并发(concurrency)和并行(parallelism)是多核编程中经常会碰到的概念,也容易混淆,首先需要区分并发与并行.

并发:用来描述存在至少两个线程正在工作(并不一定在同时执行)的状态,是一种更加通用的、可以在单核处理器上通过时间切片实现的状态.

并行:用来描述至少两个线程同时执行的状态.在同一时间点上,多个线程可同时并行执行.

1(a)所示,并发从逻辑上看是多个线程同时发生,但在物理上则是由操作系统调度完成.CPU某一时刻依然只执行某个线程的任务.所有的并发处理都有排队等候、唤醒、执行至少3个这样的步骤.当多个线程同时工作时,从宏观上看它们是并发的,从微观上看它们却又是顺序被处理的,只不过资源不会被阻塞(一般是通过时间片轮转).而并行则是物理上多个线程同时发生.如图 1(b)所示,同一个时刻,有3个线程同时运行,无论从宏观还是微观来看,3个线程都是同时工作的,也即是并行执行的.

通常而言,并发编程会涉及到较多系统资源竞争,比如同时读写共享内容、操作系统层面的线程调度等.而并行编程则较少出现严重的资源竞争现象.如GPU平台的编程,同一时刻可以存在大量同时执行的线程,因而带来高吞吐量.但是,由于GPU架构中计算能力较强,逻辑控制单元较弱,GPU不适合开发逻辑较为复杂的并发程序.

在多核出现前的单核处理器时代,经过几十年的发展,人们已经在编译器优化、处理器优化、多线程编程等方面有深入的研究,开发了对编程人员而言非常抽象的各种基础库,编程人员无需知道底层硬件设备、编译器、OS调度等实现细节.进入多核时代后,由于计算机CPU架构的变化,相应的OS调度、编程模型等的调整才刚刚开始,一个高度抽象的基础库还没有形成,因此,多核时代的并发编程更加困难.

1.2 数据结构并发的重要性

为了更深刻地理解数据结构并发的重要性,首先让我们来看看著名的阿姆达尔定律(Amdahl’s law)[].设n为可并发执行线程数,p为可并发执行部分所占整个程序的比例,那么最后可以得到这个程序最优情况下能够达到的加速比S.

通过上述公式可以推出S并不是和n线性相关的.举一个直观的例子:假设目前可并发执行的线程数为10,那么理想情况下可以达到的加速比是10.然而,根据上面的阿姆达尔定律,当并发执行的程序比重占到90%时,最后只能得到5.2倍的性能提升,仅为理想值的一半左右.因此,阿姆达尔定律揭示我们,对一个程序进行并发改造的空间是有限的,主要的影响因素是CPU核心数目和程序必须顺序执行的部分占整个程序的比例.在上述例子中,剩下不可并发执行的10%部分对于整个程序并行效率的提升相当重要,而这部分在程序中通常体现为需要内部调整、具有特殊性质的数据结构.因此,只有提升了这些关键数据结构的并发能力,才有可能给整个应用带来很大的性能提升.

演进条件是指并发数据结构活性能够被保障的条件.它是用来评价并发数据结构的重要指标,意味着并发数据结构算法最终在怎样的情况下完成[].演进条件在一定程度上反映了并发数据结构的性能.根据演进条件的差异性可以将其主要分为4个大类别:阻塞(blocking)、无干扰(obstruction-free)、无锁(lock-free,简称LF)和无等待(wait-free,简称WF),这4个类别又可以被细分为如图 2所示的7个不同小类.分析已有的演进条件,归纳它们之间的定义及关系如下.

● 阻塞:即这种方法在其执行过程中不能正常运行,直到其他(占有锁的)线程释放.阻塞是大家所熟知的,基本上,所有加锁的算法都可以说是阻塞的.使用阻塞算法时,某个线程所引起的意外延迟会阻止其他线程继续运行.在极端情况下,会造成死锁;

● 无饥饿(starvation-free):那些希望进入互斥区域的线程最终都能够进入互斥区域(即使之前在互斥区中的线程意外停止了).无饥饿有的时候也被称为无闭锁.

● 无干扰:如果一种方法满足无干扰性质,那么这种方法从任意一点开始它的执行都是隔离的.当一个线程调用该无干扰方法时,其他任何线程调用的各种方法都不会对这种无干扰方法的运行结果造成实质性影响,但是该方法的运行时间可能由于其他线程占用资源而受到影响,但是绝对不会出现死锁.

● 无锁:如果一种方法是无锁的,那么它保证有一些调用能够在有限步内完成.

● 无等待:假如一种方法是无等待的,那么无论其他操作如何,这种方法的每一次调用都可以在有限的步骤内结束.无等待是一种非阻塞的演进条件,意味着一个线程的任意意外延迟(比如说一个线程持有锁)都不会阻塞其他线程的继续执行.

● 有界无等待(wait-free bounded,简称WFB):如果一种方法是有界无等待的,那么这种方法保证每次调用都能够在有限并且有界的步骤内完成.这个界限可能依赖于线程的数量.

● 集居数无关无等待(wait-free population oblivious,简称WFPO):一种无等待的方法,如果其性能和活动线程数目无关,那么被称为集居数无关无等待的.

多核时代的数据结构基本上都可以按照上述7类演进条件来进行划分,从演进条件的活性保障程度来说,存在如下关系:集居数无关无等待>有界无等待>无等待>无锁>无干扰>无饥饿>阻塞.演进程度越高,意味着线程的活性能够得到更多的保障,多线程在执行时受到的干扰和约束越少,因此执行的效率更高.演进程度高的涵盖演进程度低的.对于阻塞和非阻塞两大类来说,若一个数据结构满足演进程度高的条件,那么它也一定满足演进程度低的演进保证.举个例子:我们可以说“无等待意味着无锁”(但是不能反过来说).这意味着一种方法如果满足无等待,那么这种方法就有着与一种无锁方法同样的演进保证.非阻塞类的演进条件涵盖关系可以用维恩图的形式来表达.

在设计并实现并发数据结构时,其能够达到的演进条件取决于应用的需求和底层软硬件平台的支持.一般来讲,演进程度高会具有比演进程度低更好的效率.

3 各类数据结构并发研究现状

近30年来,科研人员对各种数据结构并发操作的研究步伐一直没有停歇过,研究的重点也逐步从最初简单的数据结构(如:Stack,Queue,Hash-Table,Skiplist等)转向复杂树形结构.在这一节中,我们将对这些主要数据结构的研究情况进行分析.

堆栈(stack)是较为简单的数据结构.在前人深入研究的基础上,Michael和Scott在文献[]中总结了多个基于锁的并发堆栈实现,其基本原理是利用全局锁对堆栈访问进行控制,然而,这种设计使得堆栈的栈顶成为并发的瓶颈.Treiber在文献[]中首次提出了演进条件达到无锁级别的并发堆栈,它采用单链表来表示堆栈,使用CAS(compare-and-swap)原子操作来修改堆栈的栈顶.实验结果表明,相对于基于锁的实现,这种无锁实现在性能上具有很大优势.

4所示,当多个线程(线程A和线程B)进行栈顶访问发生资源冲突时,使用CAS原子操作来修改堆栈的栈顶.同一个时刻只有一个线程成功(线程A),另外一个线程B失败后则进入下一轮CAS竞争.这样在对资源的竞争中避免使用锁,取得了比较高的并发性.除此之外,文献[, ]中提出一种称为“消除(elimination)”的技术来提高堆栈的并发性,其核心思想是允许成对的彼此相反的操作在不经过中央调度的情况下立即完成.因此,当一个pop操作遇到一个并发执行的push操作时,允许pop操作立即获得push操作的值,并且这两个操作都立即返回.

最早的无锁并发队列出现在文献[]中,其核心思想是通过一个有限长度的数组来实现无锁功能,两个独立的线程分别在队列的入口和出口负责读操作和写操作.在此基础上,文献[]提出了一个基于链表的无锁队列.文献[]提出一个数组容量无限、基于无锁数组的队列.文献[]对无阻塞队列的实现作了详细总结,并提出一种基于CAS原子操作且演进条件为无锁的队列,通过CAS来对队列的头尾进行多线程的资源互斥和并发控制.上述工作[, , , ]都基于单端队列,双端队列的并发设计则比普通队列难很多,因此目前实现的大多是基于锁的阻塞双端队列.文献[]指出,即便使用CAS,也很难设计出无锁的双端队列.目前已知的实现是Herlihy在文献[]中提出一种基于CAS、满足演进条件为无干扰的双端队列.

最早的并发链表是一种基于全局锁的实现,每一个获得全局锁的线程进入链表进行操作,其他线程则在外等候,因而全局锁的粒度较粗.一种称为“手连手锁(hand-over-hand locking)”的细粒度锁在文献[, ]中被提出,在这种方法中,链表中的每个节点都有一个锁的标志位,线程在遍历链表时,只有当获得下一个节点的锁之后才会释放当前节点的锁.相对于全局锁,手连手锁的特点是缩小了锁影响的节点范围,一个链表中可以同时存在多个线程进行操作,提高了链表的并发性.但是,在相互临近节点的插入和删除操作还是会相互干扰,影响了结构的并发性.Valois[]首次提出了基于CAS的无锁链表,但是Valois的设计过于复杂而难以实现.在此基础上,Harris[]提出了一种更易于实现、演进条件为无锁的链表,通过为节点设置一个删除标志位,只允许用原子操作来修改这个标志位.在这种实现中,节点被设置成删除后并不是马上就物理删除,而是先存在于链表中,等待内存垃圾回收时再统一删除.

通过为哈希表的每个存储单位分配一个读/写锁,固定长度哈希表的并发操作相对容易实现[].然而,随着插入元素的增多,只有可扩展的哈希表才能保障良好的性能,但设计并发的可扩展哈希表难度较大.文献[, , ]提出了一种适用于分布式数据库的、基于两层锁的可扩展哈希表.在此基础上,Lea在文献[]中提出了可扩展哈希表的算法,在测试中表现出了良好的性能.该算法不对每一个存储单元加锁,而是建立少量更高层的锁.在哈希表进行调整时允许进行并发查找,但是不允许并发的插入和删除操作.上述工作[, , , ]都是基于锁的可扩展并发哈希表来实现,这种方法不可避免地存在阻塞同步的缺陷,并且当哈希表的容量进行扩充时需要重新调整元素的分布,这使得阻塞情况更加严峻.Shalev和Shavit[]首次提出一种演进条件为无锁的可扩展哈希表,其核心思想是将哈希表中的每个元素放到一个无锁的单向链表中.由于单向链表本身是无锁实现的,因此对其中每个元素(也就是哈希表中的每个元素)的操作(插入、删除)也就是无锁的.单向无锁链表的引入使得对元素的查询具有O(n)的时间复杂度,这有悖于哈希表中O(1)的属性.为了解决这个问题,Shalev和Shavit在算法中维护了一个可变长的指示数组,数组中存储了指向相应单向链表节点的指针.通过这种方式,使得查询操作不再是顺序查找的过程,而是能够通过数组中的指针快速定位到相应的链表节点的过程.

并发技术也被引入一些新近提出的Hash算法中以提高效率.Cuckoo hashing是Pagh等人[]于2001年提出的一种解决hash冲突的方法,其基本思想是使用两个hash函数来处理碰撞,通过多步探测,以增加查找与插入时间为代价减小hash碰撞,提高了hash

传统的并发数据结构(如Linked-list,Stack,Queue,Hash-Table,Skiplist等),由于结构相对简单且研究时间较长,都已经做到无锁实现.对于更高程度的无等待并发,由于研究难度较大,目前进展缓慢.因此,近年来科研工作者将研究重心转向并发树型结构的研究[, , , memory)实现的并发红黑树结构,虽然这种实现非常方便,不需要程序员进行额外的同步处理.然而,STM实现普遍导致了巨大的时间开销,一个简单的细粒度锁二叉树结构,或者是非阻塞二叉树结构可以在性能上轻易地超过它.文献[]提出了一种基于细粒度锁的并发平衡二叉树数据结构.该二叉树结构利用了乐观并发的思想,利用版本号控制技术来判断操作是否冲突:如果冲突,那么算法从根节点开始重新遍历;否则,操作完成.Ellen等人[]首次提出了利用CAS原子操作的并发二叉树结构,该结构属于“外部树”结构,即所有键值存储在叶子节点,并且删除时将父节点和叶子节点一起删除,而不对其他内部节点作任何处理.文献[]中将待删除节点的后继节点覆盖到待删除节点,如果后继节点只有一个孩子,那么将后继节点删除,否则,保留后继节点(采取逻辑删除的思想).上面提到的并发树[]本质上都是基于外部树,其结构如

图 5所示,所有外部树的信息都存储于叶节点,中间节点起到路由作用.因此,相对于普通内部树,外部树会导致一定的额外存储空间开销,但是这样做的好处是在插入、删除时避免了树型结构的大范围调整,有利于多核多线程的并发操作.如图 5(a)所示,在内部树中删除节点15时,需要将底层节点20调整到顶部,影响范围比较大,而外部树则避免了这种情况,减小了因为节点调整影响的范围.

Natarajan等人在文献[]中利用CAS和SETB操作设计了一种高效的并发二叉树数据结构,大幅度提升了二叉树的性能.Drachsler等人[]针对二叉树中的查找操作进行优化(这里提到的查找操作指的是查询、插入和删除都需要涉及的定位节点操作),主要利用了逻辑有序(logical ordering)的思想,即存储每个节点逻辑上的前后节点,将插入和删除操作的查找行为和查询操作的查找行为分离,从而提升查找操作的效率.他们也提到了关于在提升查找操作的性能、额外的存储空间以及调整Logical order的额外时间之间存在一个权衡的问题.文献[]结合了前人的技术要点,构造了一个高效的二叉树结构.该二叉树结构具有以下几个特点:(1) 采用内部树以及线索树结构,并且通过一些技巧加以优化,使得操作的复杂度从O(cH(n))降低到了O(H(n)+c).(2) 仅使用CAS原语,因此跨平台可用.在二叉树研究的基础上,Brown等人将研究的范围扩展到多叉树(k-ary tree),并且着重讨论了多核情况下多叉树中的范围查询问题,实验结果显示,基于CAS的实现在小范围查询时取得了显著效果[, ].

对于需要平衡的树形结构来说,插入、删除操作都会带来树形结构的调整,因此,前人还针对并发二叉树的平衡性策略进行了相关研究.文献[]在每一次互斥操作之后进行上锁调整平衡,而文献[]则采用一个额外的线程专门进行调整操作.有的时候,可以通过弱化平衡操作的实效性来减小数据结构设计的难度,如文献[]通过将树的平衡操作分离实现了一个平衡二叉树,其核心思想是在进行插入、删除操作时并不立即进行平衡调整,而是等到一定时间后再进行平衡调整.

目前所有已知实现的树型结构,能够达到的最高演进条件是无锁级别.

4 并发数据结构关键技术

在上一节中,我们对现有数据结构的并发研究现状进行了分析.可以看出,每一种并发数据结构的设计都具有一定的难度,需要选用特定的技术来精心设计,我们将其中一些关键技术进行分析总结如下.

通用构建是对不同数据结构提供一套在多核环境下的统一构建方法.有两种不同的实现:(1) 将数据结构的所有并发操作都放入队列中来生成对应的顺序化操作[].(2) 对数据结构建立多个副本,并发操作在不同的副本上得到执行,更新受到影响的共享结构部分,完成不同副本之间的一致性保证[, ].由于其普适性的设计思想,通用构建方法相对于采用特殊设计技术(如:基于锁的实现)的执行效率较低,除此之外,在一些复杂结构(如树型结构)中很难采用通用构建方法.

基于锁的实现是目前多核并发编程中采用最为广泛的技术.根据锁的粒度的不同,可分为粗粒度锁和细粒度锁.

粗粒度锁主要解决线程之间同步和互斥问题,在Java编程中体现为管程(moniter)的概念.管程是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源.通过使用管程,可以针对管程对象的每一种方法自动获取锁资源,释放锁资源.与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程很大程度上简化了程序设计.管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序.采用粗粒度的锁可以简化加锁行为,减少编程难度,但是粗粒度锁对并发性影响更大.在第3.3节中提到的全局锁就是一种粗粒度锁.

相对于粗粒度锁在线程层面实现并发,细粒度锁可以在数据结构层面实现并发.几乎所有数据结构(如:堆栈[]、队列[]、链表[, ]、哈希表[, , ]、树形结构[])等都可以采用细粒度锁来实现多核数据结构并发.这种技术在简单数据结构(例如:堆栈队列、哈希表、跳表)中实现的效果很好,然而在树形结构,特别是需要调整平衡的树形结构中效率却不高.其根本原因在于,基于细粒度锁的实现需要对树形结构中附近区域内的多个节点进行加锁操作,同时只允许一个线程进入共享区进行操作来保障共享数据的安全.对于需要平衡的树形结构来说,插入、删除操作都带来树形结构的调整,这就意味着加锁的范围会很大,降低了数据结构的并发性.Leo[]通过将树的平衡操作分离实现了一个平衡二叉树,其核心思想是在进行插入、删除操作时并不立即进行平衡调整,而是等到一定时间后再进行平衡调整.基于这样的思想,Nurmi放松了平衡的条件,实现了chromatic树[],这是一种采用面向叶子(leaf-oriented)技术的红黑树.在此基础上,Boyar通过修改chromatic树的平衡条件提高了其性能[],并首次给出了完整的正确性证明.

手连手锁是一种特殊的细粒度锁,在这种方法中,每个节点都维护一个锁.线程在遍历时,只有当获得下一个节点的锁之后才会释放当前节点的锁.相对于全局锁,手连手锁的特点是缩小了锁影响的节点范围.文献[]提出了一种基于手连手锁的并发平衡二叉树数据结构.在文献[]中提出了一种基于手连手锁的红黑树结构.

综合近几年来二叉树研究的情况,在多核时代的树形数据结构设计中,基于细粒度锁的实现会对树中一定范围的节点进行锁定,这在一定程度上会对并发的更新操作产生影响.

此外,由于基于锁的技术能够有效保护共享数据,一些特殊设计的锁(如:自旋锁(spinlock)、读写锁(rwlock))在Linux操作系统中得到了广泛的使用.传统上,当发生访问资源冲突时,可以有两种选择:一种是死等,另一种是挂起当前进程,调度其他进程执行.spin lock就是一种死等的机制,当前的执行线程会不断地重新尝试直到获取锁进入临界区.因此,自旋锁在同一时刻只能被最多一个内核任务持有,所以,一个时刻只有一个线程允许存在于临界区中.在多核时代,自旋锁总是只允许一个CPU核访问共享资源,其余CPU核均是忙等待,无法发挥多核的优势.考虑到这样的情况,Linux中还提供了读写锁,该方法允许多个CPU核读者同时访问,绝对限制只有一个核可以作为写者.当读者访问资源时,写者必须忙等待,反之亦然.然而,随着计算机硬件的快速发展,获得各种锁的开销相对于CPU的速度在成倍地增加.在大部分非x86架构上获取锁使用了内存栅(memory barrier),这会导致使用锁的时候处理器流水线停滞或刷新,因此使用锁的开销相对于CPU速度而言就越来越大,因此,一种能够提供高并发性的同步机制——Read-Copy-Update(RCU)[]即被提出,它允许reader和writer并发地访问共享数据,支持一个writer和多个reader之间的并发.通过维护多个版本的数据,RCU保证了reader读取到的数据的一致性,还保证在reader完成读取前,被访问的数据不会被释放.

综合近几年来二叉树研究的情况,在多核时代的树形数据结构设计中,基于细粒度锁的实现会对树中一定范围的节点进行锁定,这在一定程度上会对并发的更新操作产生影响.

图 6所示,对于读者,在访问被RCU保护的共享数据时可以直接访问,不用关心这个数据是否被锁住;而对于写者,在进行更新操作时,首选需要获得该数据的一个拷贝(如图 6中步骤2所示),然后对该拷贝进行更新,在没有任何读者来读取该数据时,将这个原始数据指针修改为指向该更新拷贝(如图 6中步骤3所示),于是就完成了修改操作.RCU的好处是读操作是无锁的,所以不存在同步问题,也不需要内存栅栏,对于由于锁机制导致的死锁和内存延迟等问题都有较好的改善.写操作的同步开销相对较大,因为需要等待合适的更新数据的时机,以及增加了原始数据释放、修改指针等操作,如果当前存在多个写者,依旧需要使用锁机制来同步其他写操作.RCU在Linux内核2.5.43版本开始使用,截至2014年,已经使用了超过9

在2015年召开的SOSP会议上,Alexander等人[]提出了RCU的改进版RLU(read-log-update)技术.相对于RCU只允许多个读、一个写机制来说,RLU的最大优势是允许多个读、多个写同时进行,提高了并发性.

control,简称OCC)是一种并发控制的方法,首先出现在数据库系统[]的相关工作中.其核心思想是让每个线程分离执行,使得每个线程执行时并不受到其他线程的干扰,当线程需要对数据结构作修改时,再对比前后是否保持一致性状态,如果确认状态一致,那么说明在这段时间内没有任何其他线程对数据结构做过修改,该线程提交修改,并且更新版本号;否则,线程需要回滚重试.

memory,简称STM),被用来设计并发树[]和并发平衡树[].后来,OCC被用来对并发二叉树中查询操作进行优化[].值得注意的是,演进条件“无干扰”本身就具有乐观并发性,即旧值和新值比较时,只有不存在冲突时才能避免重试.无干扰的演进条件屏蔽了线程操作的中间状态,而通过数据结构的前后版本对比来确定前后是否保持一致.显然,这种演进条件比无锁和无等待要弱得多,因为它并不能保证在某一时刻至少一个线程是处在演进状态的(可以把演进看作是数据结构的一种不断改变的状态).然而,这种演进条件也有一定的好处,其实现思想简单,极大地简化了设计并发数据结构的过程,程序员不用花时间去思考如何验证数据结构的正确性.乐观并发控制的思想并不一定用在非阻塞技术中,如文献[]中提出了一种基于细粒度锁的乐观平衡二叉树,文献[]中提出了一种易于证明的乐观并发跳表.

事务性内存可分为两类:软件事务内存(STM)和硬件事务内存(HTM),硬件事务内存的概念很早于1986由 Herlihy[]提出,然而直到最近的Haswell处理器上,才开始提供硬件事务内存接口[].借鉴硬件事务内存的思想,1995年,麻省理工大学的Shavit教授在其论文中首次提出软件事务内存的概念[],并因为在该领域的一系列杰出贡献获得了2012年分布式领域著名的Dijkstra奖.软件事务内存的核心思想是假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据.在提交数据更新之前,每个事务会先检查在该事务读取数据后有没有其他事务又修改了该数据.如果其他事务有更新的话,正在提交的事务会进行回滚.在数据冲突较少的环境中,偶尔事务回滚的成本会低于读取数据时锁定数据的成本,因此可以获得比其他并发控制方法更高的吞吐量.

软件事物内存是并发实现技术中锁的一种替代机制,它的出现引起了研究人员和开发人员的高度关注,分别从编程语言、编译器、指令执行等各个软件层面进行了深入研究[, , , , , , , , , ].目前主流语言都在不同程度上支持软件事务内存[],其中,Clojure将软件事务内存集成在其核心语言中.相对于其他并发实现技术,软件事物内存对于程序开发人员的好处在于,简化了并发设计中对于资源共享、冲突部分的软件设计.相对于传统意义上广泛采用的加锁技术,STM的概念要简单得多,因为每一个事务可以理解为隔离的,就像只有单个线程进行操作一般,而死锁等特殊情况都由系统来控制完全加以避免,程序员无需担心这些问题.软件事物内存简化了无阻塞编程的难度,然而,相对于成熟的基于锁的技术,软件事物内存需要记录每次操作日志,因而带来不小的开销,在高并发情况下,不断的事物回滚减弱了多核数据结构执行的效率,扩大了事物执行的顺序开销.因此,目前软事物内存尚未成熟,远未达到实际系统中大规模采用的程度[,

资源竞争是并发编程中存在的最核心问题,通常可采用互斥的方式来解决.操作系统分别从软件和硬件层面提供了支持,软件层面可以采用信号量和锁的机制,硬件层面则提供了原子操作的手段.从CPU指令执行的角度来看,软件层面的信号量和锁通常涉及多个CPU执行周期,而硬件层面的原子操作则能够由一条指令完成,因此原子操作执行速度最快,这使它成为高速并发数据结构设计的首选.原子操作具有不可分割性,在执行完毕之前不会被任何其他任务或事件中断,也不会被线程调度机制打断.在并发编程中,经常采用的原子操作包括:

需要指出的是,MCAS可以同时对多个内存单元的数据进行compare-and-swap原子操作,因此在并发数据结构设计中更具灵活性,但是MCAS的最大缺点是可移植性差,很多硬件并不提供支持.原子操作使得并发线程对资源竞争的相应速度达到最大化,同时也保证了对并发线程的隔离.

标志位是近年来多核数据结构非阻塞算法中最为核心的辅助技术,主要用于让线程对竞争区域的操作可见,即明确其他线程对自身线程操作区域的动作.因此,标志位的好处在于使得其他线程不必等待占有临界区的线程操作,而是能够感知临界区的当前操作,从而做出相关动作.对标志位的修改只能用原子操作来实现,这样才能做到线程之间的绝对隔离.

文献[]中最早提出了一种用标志位和CAS操作实现的无锁链表,Michael[]在前人的基础上,改善了内存管理机制,并且大量优化了并发效率.Lea在此基础上实现了ConcurrentSkipListMap,并集成到java.util. concurrent中.该并发跳表被认为是最快的并发跳表,经常被用来和并发平衡二叉树、乐观并发跳表等实现作效率对比.2010年,Ellen等人[]将标志位和原子操作引入平衡二叉树的设计中,极大地降低了由于插入、删除操作造成的平衡开销,首次实现了无阻塞的平衡二叉树,取得很大的突破.从第3.5节的分析中我们也可以看出,标志位和原子操作是近几年并发树型结构采用的主要技术.值得注意的是,对于原子操作的选取需要慎重考虑底层硬件的支持,否则整个结构的可移植性和实用性都不高[].

应用标志位和原子操作的示意图如图 7所示,每个节点都维护一个标志位,初始状态为CLEAN,当需要进行插入或删除操作时,多线程采用CAS原子操作进行竞争,只有一个线程能够进入插入循环圈(如图 7所示,在CLEAN右边)或删除循环圈(如图 7所示,在CLEAN左边)进行活动,这样使得进行并发操作的多个线程得到隔离,保证了并发操作的安全性.以插入循环圈为例,只有一个线程进入,其他线程在外面等候,通过iflagCAS操作将该节点标志位从CLEAN设置为IFLAG,然后,通过iflagCAS操作将孩子节点的标志位从CLEAN设置为IFLAG,等到插入完成后,再利用iunflagCAS操作将该节点和孩子节点的标志位一同恢复到CLEAN.

Kogan在2011年[]提出了首个满足无等待演进条件且高效的队列结构,该结构的基本思想是将所有线程的操作记录到线程数组中,使得后来的线程能够帮助之前的线程完成操作,因而每一步操作都可以在固定步骤内完成.这种技术参考了文献[]中提出的快速和慢速通路方法(fast-path-slow-path

目前,已有多核数据结构都是特定构建的,支持的功能以及采用的关键技术都存在很大差别,并没有形成一套系统化的规范准则.上述6种核心技术各有特色,在多核数据结构设计中需要慎重选取.在对它们进行分析后得到如下对照表(见表 1).

表 1所示,不同核心技术在已实现结构中达到的最大演进条件各不相同,线程监控数组技术和通用构建实现的演进条件最高,达到Wait free级别.但是,基于线程监控数组的方案很难实现,实际应用很低.通用构建在几种情况下得分都比较落后,因此不适合作为实际选取的开发技术.基于锁的方案虽然是一种阻塞实现,且运行效率为中等,但是由于从单核时代就已经大量运用,程序员较为熟悉,因此实际应用最广泛.标志位、原子操作和乐观并发控制具有运行效率高等特点,但是对程序员友好程度低,开发难度较大,实际研究和应用刚刚开始,这也意味着它们是目前最具研究潜力的两项技术.

5 并发数据结构开发 5.1 并发数据结构开发流程

设计并实现并发数据结构是一个复杂的过程,需要涉及很多概念和步骤,整个过程可以总结如图 8所示.

图 8所示,并发数据结构的设计和实现工作可以总结为3个主要步骤:(1) 分析;(2) 设计;(3) 实现和验证.

首先通过实际需求调研,选择合适的数据结构.然后查看国内外文献,以及搜索开源代码来确定这些数据结构是否已有成熟的实现.对于已有开源实现的,分析其工作原理、性能和已经达到的演进条件,可作为下一步设计的参考对比.对于还没有实现的,则需要明确演进条件,作为下一步设计的基础.就目前的软硬件条件来说,对多核数据结构的演进条件(意味着能达到的性能)的要求主要集中在无锁层面,对于更高的演进条件(集居数无关无等待、有界无等待和无等待),由于实际完成难度太大,因此建议慎重考虑.

根据演进条件、对程序员的友好程度、结构执行的效率、应用场景中对于数据结构中执行不同操作的需求(如:侧重读访问或是读写均衡)等因素来选择实现技术手段.例如:如果想要实现一个阻塞的、执行效率高的数据结构,则基于锁的实现是一个很好的选择.如果想要实现一个obstruction-free的、程序员开发容易的数据结构则可以考虑软件事物内存这种方式来实现.如果应用需求中读操作很多,而插入、删除比重很小,则可以考虑利用OCC技术来实现.最后,再综合考虑数据结构具体的组织形式和相应的插入、删除、查询、调整等算法.合理的技术路线选择能够在很大程度上指导和简化之后的算法设计和实现.

选用合适的语言来实现,一般建议选择自己最熟悉的语言,这样可以减少开发难度.此外,选择的开发语言必须能够支撑步骤(2)中选择的实现技术手段.例如:在步骤(2)中确定要选择软件事务内存,那么所选用的开发语言需要支持.如果在步骤(2)中是选择基于锁的实现,因为不同开发语言对锁的实现其实差异很大,所以也需要认真评估.选定开发语言后,接下来就是按照之前的设计,具体实现这个数据结构.然后分别从理论和实验角度验证数据结构的正确性,并通过实验来验证相应结构的性能.

5.2 Java中现有并发数据结构实现情况

由于并发数据结构存在设计和实现上的难度,因此真正已发布的、稳定的并发库其实并不太多.Java中的java.util.concurrent是极少数得到广泛应用的软件包,现已在并发编程中成为很常用的工具类.在Java 5之前,Java中如果需要处理并发,那么通常需要使用wait()、notify()和synchronized手工实现并发处理的代码,加上需要考虑性能、死锁和资源管理等因素,开发的负担会很重.从Java

OnWriteArraySet.由此可见,目前Java 8版本中只提供一些最基本的并发数据结构,对于一些复杂的结构,则需要编程人员自己实现.

6 多核CPU数据结构正确性理论分析方法

设计出多核CPU数据结构后,就需要评估设计的正确性,通常有两种手段可选:实验的方法和理论分析的方法.实验的方法通常比较直观,理论分析的方法则逻辑性要求更强,表达起来也更困难.

通过对前人在多核CPU数据结构正确性分析方法进行对比研究,总结如下.

结构不变性主要用来说明并发操作并不会改变原始数据结构的属性.每种数据结构都有其自身特点的数据组织形式(如:链表和树的结构就不同),这些性质在数据结构被创建时就已成立,并且任何操作都不能改变这些性质.因此,当多线程并发地对多核CPU数据结构进行操作时,无论经过多少次插入、删除、查找,最终得到的还是同种性质的数据结构(如:初期是二叉树,经过N次并发操作后还应该是二叉树.不允许出现初期是二叉树,经过N次并发操作后变成m叉树).在具体分析说明时涉及到两个关键步骤:(1) 列举出这种结构的不变性质;(2) 对每一种操作都分析其不会改变原来数据结构的性质.举个例子:如果现在来分析并发跳表的结构不变性,则需要首先列举出跳表的结构的不变性质:(1) 哨兵节点不能改变.(2) 每一层的节点都按照关键字排序,并且不会出现重复的关键字.(3) 下层节点的节点数一定不少于上层节点.然后,再分别对查找、插入、删除、置标志位等操作进行分析,说明这些操作并不会改变其跳表的属性.

安全性意味着对并发数据结构的操作不会出错[].对安全性的一个重要评价是可线性化,其基本思想是每一个并发的经历(history)都等价于一个顺序的经历.通常来说,用来说明并发数据结构的可线性化性质的方法就是指出该算法的可线性化点(linearizability point),然后指明在该线性化点的并发操作是如何映射为不同线程的等价顺序操作的.

当考虑并发数据结构的活性时,我们期待对这个数据结构的操作最终都能够完成[].活性意味着并发数据结构算法最终在怎样的情况下完成.通常可以用演进条件来表示,如无死锁、无饥渴、无锁、无等待等.如果保证算法调用不会因为互相等待而无法获取资源,那么称其为无死锁.如果保证算法调用最终都能取得请求的资源,那么称其为无饥渴.如果保证算法某次调用能在有限的步骤内完成,那么这种方法是无锁的.如果算法每次调用总是在有限步骤内完成,那么这种方法是无等待的.

随着商用多核(multicore)和众核(many-core)系统越来越普及,软件中数据结构对多核支持的需求也就越来越迫切.因此,如何在多核时代提升数据结构的性能成为研究的热点.结合已有相关研究成果及其发展趋势,我们认为,以下有关研究将是研究者近年来所关注的热点问题.

基于硬件事务内存的并发数据结构研究

支持无等待的高效可实用的数据结构研究

按照多核数据结构演进条件的定义,演进条件越高,意味着活性越高,也在一定程度上表明其性能越好.在目前已经实现的数据结构中,只有采用线程监控数组技术实现了高效、实用的无等待的队列,虽然采用通用构建也可以得到无等待的数据结构,但是这些实现效率都不高[, ].因此,对于很多并不具有无等待性质的数据结构来说,总是有部分比例的线程在多核执行时存在等待的时间.在这个问题上,任何高效、实用的数据结构(如:哈希、链表、树等)研究的突破都将产生较大的实际影响.

基于标志位和原子操作的无锁带调整复杂树型结构研究

标志位和原子操作是近几年来并发树型结构研究的重点,已在平衡二叉树上得到实现.然而,对于一些更加复杂的树形结构,如红黑树、空间树、前缀树、后缀树等,由于其结构组织本身具有特殊性或者存在特殊的调整策略,目前还少有文献涉及,因此这方面具有比较大的研究空间.

分布式系统的多核数据结构研究

现在大型系统都是分布式架构的,文献[]对分布式系统中不同机器的内存建立了一个统一的映射,并在上面建立B+树.这样,对于用户来说,看到的是一棵统一的B+树,然而,B+树上不同节点是映射到不同机器的内存中的.文献[]中B+树的多核实现是基于锁的传统实现.那么,如何发挥多核优势,研究并设计出适应分布式环境的无锁数据结构也将是研究者关注的热点.

总之,多核的出现给我们带来机遇的同时也带来了巨大的挑战.基于共享内存的多核数据结构已成为研究者关注的热点问题.为了有效发挥硬件多核的优势,多核数据结构及其相关研究还存在许多挑战性问题,需要不断地去探讨和研究.

}

中的WEB服务,就是基于SOAP。

简单对象访问协议(SOAP)是W3C组织的一个Note, 它描述了一种在分散的或分布式的环境中如何交换信息的轻量级协议。SOAP是一个基于XML的协议,它包括三个部分:SOAP封装(Envelop),封装定义了一个描述消息中的内容是什么,是谁发送的,谁应当接受并处理它以及如何处理它们的框架;SOAP编码规则(Encoding Rules),用于表示应用程序需要使用的数据类型的实例;SOAP RPC表示(RPC Representation),表示远程过程调用和应答的协定;SOAP可以和多种传输协议绑定(Binding),使用底层协议交换信息。在这个文档中,目前只定义了SOAP如何和HTTP以及HTTP扩展进行绑定的框架。

SERVICE可以相互对应,可以使用WSDL作为这种通信方式的描述文件,利用WSDL工具可以自动生成WS和用户端的框架文件,SOAP具备把复杂对象序列化捆绑到XML里去的能力。

SOAP的前身是RPC, 就是远程呼叫处理的协议,这个协议安全性不是很好,多数防火墙都会阻挡RPC的通信包,而SOAP则使用HTTP协议作为基本的协议,使用端口80使得SOAP可以透过防火墙,完成RPC的功能。

SOAP协议和HTTP协议一样,都是底层的通信协议,只是请求包的格式不同而已,SOAP包是XML格式的,现在我们编写WEB SERVICE不需要深入理解SOAP也没关系。如果SERVICE和CLIENT在同样的环境下使用SOAP,由于一般情况下都有自动生成SOAP程序框架的工具,因此不知道细节也没关系. 可是,

在URL前加https://前缀表明是用SSL加密的。你的电脑与服务器之间收发的信息传输将更加安全。 Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定。 http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议

它是一个安全通信通道,它基于HTTP开发,用于在客户计算机和服务器之间交换信息。它使用安全套接字层(SSL)进行信息交换,简单来说它是HTTP的安全版。
它是由Netscape开发并内置于其浏览器中,用于对数据进行压缩和解压操作,并返回网络上传送回的结果。HTTPS实际上应用了Netscape的安全全套接字层(SSL)作为HTTP应用层的子层。(HTTPS使用端口443,而不是象HTTP那样使用端口80来和TCP/IP进行通信。)SSL使用40 位关键字作为RC4流加密算法,这对于商业信息的加密是合适的。HTTPS和SSL支持使用X.509数字认证,如果需要的话用户可以确认发送者是谁。
https协议需要到ca申请证书,一般免费证书很少,需要交费。
http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议
http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
http的连接很简单,是无状态的
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全
1 . 信任主机的问题. 采用https 的server 必须从CA 申请一个用于证明服务器用途类型的证书. 改证书只有用于对应的server 的时候,客户度才信任次主机. 所以目前所有的银行系统网站,关键部分应用都是https 的. 客户通过信任该证书,从而信任了该主机. 其实这样做效率很低,但是银行更侧重安全. 这一点对我们没有任何意义,我们的server ,采用的证书不管自己issue 还是从公众的地方issue, 客户端都是自己人,所以我们也就肯定信任该server.
2 . 通讯过程中的数据的泄密和被窜改

    a) 主要目的是保证server 就是他声称的server. 这个跟第一点一样.
    b) 服务端和客户端之间的所有通讯,都是加密的.
    i. 具体讲,是客户端产生一个对称的密钥,通过server 的证书来交换密钥. 一般意义上的握手过程.
    ii. 加下来所有的信息往来就都是加密的. 第三方即使截获,也没有任何意义.因为他没有密钥. 当然窜改也就没有什么意义了.
  1. 少许对客户端有要求的情况下,会要求客户端也必须有一个证书.
    a) 这里客户端证书,其实就类似表示个人信息的时候,除了用户名/密码, 还有一个CA 认证过的身份. 应为个人证书一般来说上别人无法模拟的,所有这样能够更深的确认自己的身份.
    b) 目前少数个人银行的专业版是这种做法,具体证书可能是拿U盘作为一个备份的载体.
    a) 本来简单的http协议,一个get一个response. 由于https 要还密钥和确认加密算法的需要.单握手就需要6/7 个往返.
    b) 接下来才是具体的http协议,每一次响应或者请求, 都要求客户端和服务端对会话的内容做加密/解密.
    i. 尽管对称加密/解密效率比较高,可是仍然要消耗过多的CPU,为此有专门的SSL 芯片. 如果CPU 信能比较低的话,肯定会降低性能,从而不能serve 更多的请求.
    ii. 加密后数据量的影响. 所以,才会出现那么多的安全认证提示

可以将每一条数据先加载到list集合中,然后通过适配器将每一条数据加载到listview中实现listview对数据的逐行显示。

9.安卓系统的理解,优缺点
Android一词的本义指“机器人”,同时也是Google于2007年11月5日 宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统、中间件
、用户界面和应用软件组成。

Android分为四个层,从高层到低层分别是应用程序层、应用程序框架层、系统运行库层和Linux内核层。

优点:开放性,挣脱束缚,丰富硬件,Google应用
缺点:版本过多,升级过快
SIM卡里的所有文件按树来组织:
1>主文件MF(Master File)——每一块SIM卡只有一个唯一的主文件, 其他所有文件都是它的子孙, 主文件只有文件头,里面存放着整个SIM卡的控制和管理信息
2>专用文件DF(Dedicated File)——也是只有一个文件头, 里面存放着整个目录的管理控制信息, 专用文件相当于一个目录的根.
3>基本文件EF(Elementary File)——既有文件头,也有文件体, 文件头存放该文件的位置和控制信息, 文件体存放真正的数据, 整个SIM卡中只有基本文件有文件体, 也只有基本文件才用来存放数据.

11.四大组件哪些能动态注册

14.内存泄露遇见过哪些问题,解决办法,回收的算法
问题:运行两次后的输出结果,程序退出了,后台仍然有两个线程在跑,无法被释放,如果是大程序,就会导致:一、耗电;二、内存泄露。
在程序销毁的时候,要通过Handler的removeCallbacks(Runnable r)方法来手动释放掉该线程,当然要把Runnable单独提出来写。使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。??

15.接口和抽象类的区别?
abstract可以修饰抽象方法,而一个类只要有一个抽象方法,就必须用abstract定义该类,即抽象类。
用interface修饰的类,里面的方法都是抽象方法,因此在定义接口的时候,可以直接不加那些修饰,系统会默认的添上去。接口里面的字段都是公有常量,即public static final修饰的字段。

JavaFX的需求,并且提供更多能有效增强网络应用的标准集。

18.自定义一个不带系统边框的Dialog?
首先定义样式文件style.xml,边框设计windowframe属性为@null,将背景设置为自己想要的背景,将自定义dialog继承Dialog,将布局文件载入,创建dialog,并将自己设置的样式文件加载进去,最终实现自定义的Dialog.

20.图片的异步加载的方法?
1.利用软引用来缓存图片Bitmap,用图片的URL作为缓存查找的Key;
3.如果两级缓存都没取到图片,则从服务器获取,并加入缓存;
4.加载完后通过回调接口通知UI更新;

21.瀑布流实现方式?
a.自定义scrollView,并且使用监听器模式,对其滚动到最顶部、及最底部进行监听操作。
d.根据监听器对其滚动到顶部(不做操作)、滚到最底部(加载更多数据)、正在滚动进行操作(滚动超过两屏,回收两屏之前图片回收及回滚到之前屏幕图片重载)。

22.把一张特别大的图片,分成几十张小的图片,在最短的时间内给处理?
DVM指dalivk的虚拟机。每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。而每一个DVM进程都是在Linux系统中的一个进程,所以可以认为是同一个概念。
1:Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。
2:Dalvik负责进程隔离和线程管理,每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。
3:不同于Java虚拟机运行java字节码,Dalvik虚拟机运行的是其专有的文件格式Dex 4:dex文件格式可以减少整体文件尺寸,提高I/o操作的类查找速度。
5:odex是为了在运行过程中进一步提高性能,对dex文件的进一步优化。
6:所有的Android应用的线程都对应一个Linux线程,虚拟机因而可以更多的依赖操作系统的线程调度和管理机制
7:有一个特殊的虚拟机进程Zygote,他是虚拟机实例的孵化器。它在系统启动的时候就会产生,它会完成虚拟机的初始化,库的加载,预制类库和初始化的操作。如果系统需要一个新的虚拟机实例,它会迅速复制自身,以最快的数据提供给系统。对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域。
24.嵌入式操作系统内存管理有哪几种,各有何特性 ?
1、虚拟内存管理机制: 有一些嵌入式处理器提供了MMU,在MMU具备内存地址映射和寻址功能,它使操作系统的内存管理更加方便。如果存在MMU ,操作系统会使用它完成从虚拟地址到物理地址的转换, 所有的应用程序只需要使用虚拟地址寻址数据。 这种使用虚拟地址寻址整个系统的主存和辅存的方式在现代操作系统中被称为虚拟内存。MMU 便是实现虚拟内存的必要条件。 虚拟内存的管理方法使系统既可以运行体积比物理内存还要大的应用程序,也可以实现“按需调页”策略,既满足了程序的运行速度,又节约了物理内存空间。 在L inux系统中,虚拟内存机制的实现实现为我们提供了一个典型的例子:在不同的体系结构下, 使用了三级或者两级页式管理,利用MMU 完成从虚拟地址到物理地址之间的转换。基于虚拟内存管理的内存最大好处是:由于不同进程有自己单独的进程空间,十分有效的提高了系统可靠性和安全性。 2、非虚拟内存管理机制: 在实时性要求比较高的情况下,很多嵌入式系统并不需要虚拟内存机制:因为虚拟内存机制会导致不确定性的 I/O阻塞时间, 使得程序运行时间不可预期,这是实时嵌入式系统的致命缺陷;另外,从嵌入式处理器的成本考虑,大多采用不装配MMU 的嵌入式微处理器。所以大多嵌入式系统采用的是实存储器管理策略。因而对于内存的访问是直接的,它对地址的访问不需要经过MMU,而是直接送到地址线上输出,所有程序中访问的地址都是实际的物理地址;而且,大多数嵌入式操作系统对内存空间没有保护,各个进程实际上共享一个运行空间。一个进程在执行前,系统必须为它分配足够的连续地址空间,然后全部载入主存储器的连续空间。

25.什么是嵌入式实时操作系统, Android 操作系统属于实时操作系统吗?
嵌入式实时操作系统是指当外界事件或数据产生时,能够接受并以足够快的速度予以处理,其处理的结果又能在规定的时间之内来控制生产过程或对处理系统作出快速响应,并控制所有实时任务协调一致运行的嵌入式操作系统。
嵌入式操作系统主要用于工业控制、军事设备、航空航天等领域对系统的响应时间有苛刻的要求,这就需要使用实时系统。又可分为软实时和硬实时两种,而 android 是基于 linux 内核的,因此属于软实时。
26.一条最长的短信息约占多少byte?
一条最长的短信息约占140byte

27.android中的动画有哪几类,它们的特点和区别是什么?
答:Android中动画可以分为两大类:帧动画、补间动画
1)补间动画:(你定义一个开始和结束,中间的部分由程序运算得到。就是对场景里的对象不断的进行图像变化来产生动画效果(旋转、平移、放缩和渐变))AlphaAnimation(渐变型动画)、scaleAnimation(缩放型动画)、 TranslateAnimation(平移型动画)、 RotateAnimation(旋转型动画)、
2)逐帧动画:Frame(把一连串的图片进行系列化连续播放,如同放电影的效果),它是通过播放一张一张图片来达到动画的效果;
其中Message类就是定义了一个信息,这个信息中包含一个描述符和任意的数据对象,这个信息被用来传递给Handler.Message对象提供额外的两个int域和一个Object域。
默认情况下一个线程是不存在消息循环(message 模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。
视图(view)对象:是应用程序中负责生成用户界面的部分。也是在整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。
控制器(control)对象:是根据用户的输入,控制用户界面数据显示及更新model对象状态的部分,控制器更重要的一种导航功能,响应用户出发的相关事件,交给m层处理。
android鼓励弱耦合和组件的重用,在android中mvc的具体体现如下:
1)视图层(view):一般采用xml文件进行界面的描述,使用的时候可以非常方便的引入,当然,如果你对android了解的比较的多了话,就一定可以想到在android中也可以使用javascript+html等的方式作为view层,当然这里需要进行java和javascript之间的通信,幸运的是,android提供了它们之间非常方便的通信实现。
2)控制层(controller):android的控制层的重任通常落在了众多的acitvity的肩上,这句话也就暗含了不要在acitivity中写代码,要通过activity交割model业务逻辑层处理,这样做的另外一个原因是android中的acitivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。
3)模型层(model):对数据库的操作、对网络等的操作都应该在model里面处理,当然对业务计算等操作也是必须放在的该层的。

除了在栈顶的Activity,其他的Activity都有可能在内存不足的时候被系统回收,一个Activity越处于栈底,被回收的可能性就越大。如果我们没有覆写onSaveInstanceState()方法,此方法的默认实现会自动保存Activity中的某些状态数据,比如Activity中各种UI空间的状态。Android应用框架中定义的几乎所有的UI控件都恰当的实现了onSaveInstanceState()方法,因此当Activity被摧毁和重建时,这些UI控件会自动保存和恢复状态数据。比如EditText控件会自动保存和恢复输入的数据,而CheckBox控件会自动保存和恢复选中状态。开发者只需要为这些控件指定一个唯一的ID(通过设置android:id属性即可),剩余的事情就可以自动完成了。如果没有为控件指定ID,则这个控件就不会进行自动的数据保存和恢复操作。
从而减少了很多不必要的View的创建
3)、当ListView加载数据量较大时可以采用分页加载和图片异步加载

应用场景:音频,拍摄车牌号
37.链表和数组的区别?
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
二者都属于一种数据结构
(1) 从逻辑结构角度来看
a, 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
b,链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
(2)从内存存储角度来看
a,(静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
b, 链表从堆中分配空间, 自由度大但申请管理比较麻烦.
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数
作用:HASH表主要就是提供更快的查找速度,简单来说就是分桶。
比如说你们学校的一个年级有很多班是吧? 那么现在要查找一位同学。如果不看班级直接找,就需要每个班级一个一个的查找是不? 那现在因为分了班级,我们有一个函数可以通过名字产生它的班级号。比如说王勇,二班。 那么现在我们直接hash( 王勇) = 2; 然后我们就直接从2班开始找。 如果2班有50个学生,那么我们最多查找50次对吗? 好。假设你们年级一共有10个班,如果没有这个hash函数,那么直接找,最坏可能要查找50*10=500次。!! 这个班级其实就是每个桶!! 这就是分桶。。。通过哈希函数产生哈希值,然后相同哈希值的元素放在相同的桶里边。。。!
这样可以通过hash这个间接作用减少查找的时间和次数!这就是它的意义!!
40.什么是锁?有什么用?有哪些锁?为什么需要锁?
锁是为了保证安全性,如程序运行时保证另外的程序不能再对本程序所使用到的数据进行某些操作,版本软件的“不能合并”文件不能同时被两人修改等。
锁分为:线程锁,数据库锁,SVN锁等等
1、当几个线程都用到了某个量,但是这个量却能影响程序的运行时就需要线程锁来控制一次只能由一个线程访问这个量。如果没有使用线程锁会出现争用情况。
两个或更多的线程或进程读或写一些共享数据,而最终结果取决于这些线程是如何被调度计时的。争用情况可能会导致不可预见的结果和隐蔽的程序错误。
2、数据库锁的作用是防止程序运行的时候其他的程序不能再对该程序所使用到的数据进行操作,保护数据的安全性。
3、SVN锁的作用是防止版本软件中的“不能合并”的文件(如:图形文件)不能被多人同时更改的时候进行锁定,当一个人对其进行操作时,其他人不能对这个文件再进行操作,保证了文件的安全性。
答: Android中界面部分也采用了当前比较流行的MVC框架。

  1. 视图层(View):一般采用XML文件进行界面的描述,使用的时候可以非常方
    件加载,同时可以实现Java和JavaScript之间的通信。交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Acitivity的响
    应时间是5s,如果耗时的操作放在这里,Android的控制层的重任通常落在了众多
    的Acitvity的肩上,程序就很容易被回收掉。
    3)模型层(Model):对数据库的操作、对网络等的操作都应该在Model里面处理,
    当然对业务计算等操作也是必须放在的该层的。
    在Android SDK中的数据绑定,也都是采用了与MVC框架类似的方法来显示数据。在控制层上将数据按照视图模型的要求(也就是Android

即像素,1px代表屏幕上一个物理的像素点;
px单位不被建议使用,因为同样100px的图片,在不同手机上显示的实际大小可能不同
Pixel)。sp和dp很类似但唯一的区别是,Android系统允许用户自定义文字尺寸大小(小、正常、大、超大等等),当文字尺寸是“正常”时1sp=1dp=0.00625英寸,而当文字尺寸是“大”或“超大”时,1sp>1dp=0.00625英寸。类似我们在windows里调整字体尺寸以后的效果——窗口大小不变,只有文字大小改变。
答:1)应用程序层 java语言 应用程序开发

答:最常用的布局有以下这几种:
第一种:帧布局(框架布局)FrameLayout,在这个布局中,所有的子元素统统放于这块区域的左上角,并且后面的子元素直接覆盖在前面的子元素之上,将前面的子元素部分和全部遮挡。
第二种:线性布局LinearLayout,最常用的一种布局方式,所有子控件的对齐方式,取决于如何定义 orientation的属性:vertical 垂直方向 ,如果按照这种方向所有的子控件将按照垂直的方式分布在布局上,每行只允许有一个子元素,horizontal水平方向 ,这时子控件将会以水平的方向分布在布局中。
第三种:绝对布局AbsoluteLayout,又可以叫做坐标布局,可以直接指定子元素的绝对位置,这种布局简单直接,直观性强,但是由于手机屏幕尺寸差别比较大,使用绝对定位的适应性会比较差。
第四种:相对布局RelativeLayout,允许子元素指定它们相对于其父元素或兄弟元素的位置,这是实际布局中最常用的布局方式之一。它灵活性大很多,当然属性也多,操作难度也大,属性之间产生冲突的的可能性也大,使用相对布局时要多做些测试。
第五种:表格布局TableLayout,表格布局TableLayout以行列的形式管理子元素,每一行是一个TableRow布局对象,当然也可以是普通的View对象,TableRow里每放一个元素就是一列,总列数由列数最多的那一行决定。
第六种:网格布局 GridLayout,在Android 4.0中,新引入的GridLayout网格布局,GridLayout布局使用虚细线将布局划分为行,列和单元格,也支持一个控件在行,列上都有交错排列。而GridLayout使用的其实是跟LinearLayout类似的API,只不过是修改了一下相关的标签而已,所以对于开发者来说,掌握GridLayout还是很容易的事情。

(Android 4.0的SDK已经发布,在众多的新增特性中,其中对开发者来说比较重要的特性之一,是新增的两种界面布局方式:Space和Gridlayout)

  • 今年光棍节的那天,一个人在寝室睡到傍晚才起床。我的寝室在拐弯处,小婷子来我寝室串门,我俩聊的正嗨时,一个身影在门口...

  • 圆圆是大学毕业一起进公司的女孩。 进公司没几天,她被分配去了广州,我留在了公司总部。 半年过后,我去广州出差支援。...

  • 01 读大学的时候,我们宿舍有四个人,包括我。 我第一个见到的是小猫,她很热情地跟我打招呼,还给了我一点家乡特产。...

  • 何谓蚂蚁?生活中的琐事。何谓大象?心中的理想。 最近发生在身边的两件事。第一,回家的困扰,回家让自己压抑。因为总感...

  • 在外工作的我最害怕的就是接到家里来的电话,因为家里有病重的老妈妈…… 然而今天下午却接到了,慌了,乱了……

}

我要回帖

更多关于 数据结构单链表的实现 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信