OO第二次博客

oo5_7

html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video { margin: 0; padding: 0; border: 0 }
body { font-family: Helvetica, arial, freesans, clean, sans-serif; font-size: 14px; line-height: 1.6; color: #333; background-color: #fff; padding: 20px; max-width: 960px; margin: 0 auto }
body>*:first-child { margin-top: 0 !important }
body>*:last-child { margin-bottom: 0 !important }
p,blockquote,ul,ol,dl,table,pre { margin: 15px 0 }
h1,h2,h3,h4,h5,h6 { margin: 20px 0 10px; padding: 0; font-weight: bold }
h1 tt,h1 code,h2 tt,h2 code,h3 tt,h3 code,h4 tt,h4 code,h5 tt,h5 code,h6 tt,h6 code { font-size: inherit }
h1 { font-size: 28px; color: #000 }
h2 { font-size: 24px; border-bottom: 1px solid #ccc; color: #000 }
h3 { font-size: 18px }
h4 { font-size: 16px }
h5 { font-size: 14px }
h6 { color: #777; font-size: 14px }
body>h2:first-child,body>h1:first-child,body>h1:first-child+h2,body>h3:first-child,body>h4:first-child,body>h5:first-child,body>h6:first-child { margin-top: 0; padding-top: 0 }
a:first-child h1,a:first-child h2,a:first-child h3,a:first-child h4,a:first-child h5,a:first-child h6 { margin-top: 0; padding-top: 0 }
h1+p,h2+p,h3+p,h4+p,h5+p,h6+p { margin-top: 10px }
a { color: #4183C4; text-decoration: none }
a:hover { text-decoration: underline }
ul,ol { padding-left: 30px }
ul li>:first-child,ol li>:first-child,ul li ul:first-of-type,ol li ol:first-of-type,ul li ol:first-of-type,ol li ul:first-of-type { margin-top: 0px }
ul ul,ul ol,ol ol,ol ul { margin-bottom: 0 }
dl { padding: 0 }
dl dt { font-size: 14px; font-weight: bold; font-style: italic; padding: 0; margin: 15px 0 5px }
dl dt:first-child { padding: 0 }
dl dt>:first-child { margin-top: 0px }
dl dt>:last-child { margin-bottom: 0px }
dl dd { margin: 0 0 15px; padding: 0 15px }
dl dd>:first-child { margin-top: 0px }
dl dd>:last-child { margin-bottom: 0px }
pre,code,tt { font-size: 12px; font-family: Consolas, "Liberation Mono", Courier, monospace }
code,tt { margin: 0 0px; padding: 0px 0px; white-space: nowrap; border: 1px solid #eaeaea; background-color: #f8f8f8 }
pre>code { margin: 0; padding: 0; white-space: pre; border: none; background: transparent }
pre { background-color: #f8f8f8; border: 1px solid #ccc; font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px }
pre code,pre tt { background-color: transparent; border: none }
kbd { background-color: #DDDDDD; background-image: linear-gradient(#F1F1F1, #DDDDDD); background-repeat: repeat-x; border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD; border-style: solid; border-width: 1px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 10px; padding: 1px 4px }
blockquote { border-left: 4px solid #DDD; padding: 0 15px; color: #777 }
blockquote>:first-child { margin-top: 0px }
blockquote>:last-child { margin-bottom: 0px }
hr { clear: both; margin: 15px 0; height: 0px; overflow: hidden; border: none; background: transparent; border-bottom: 4px solid #ddd; padding: 0 }
table th { font-weight: bold }
table th,table td { border: 1px solid #ccc; padding: 6px 13px }
table tr { border-top: 1px solid #ccc; background-color: #fff }
table tr:nth-child(2n) { background-color: #f8f8f8 }
img { max-width: 100% }

多线程同步策略分析

1.多线程电梯时的策略

线程分析

多线程电梯时,我还执着于时间的精准性,也就是上下楼一定要多少多少秒,所以采取的是假时间策略。

为了实现假时间策略,我将三部电梯的运行封闭到了一个线程当中,单独一个线程内部的执行是不会受到线程调度产生的误差的影响的。

在这基础上,考虑到输入IO会有阻塞,安排了一个输入线程。至于调度器线程,对于使用假时间策略的我的设计而言,它是可有可无的,毕竟实际的调度都是在电梯线程内部进行的,如果不在电梯线程内部进行,那么调度与执行之间就会产生线程调度导致的时间误差,会导致正确性问题。故而我的调度器线程虽然按照指导书要求而加上了,但它仅进行部分不会产生时间误差的调度功能。

同步策略

在输入线程与调度器线程之间仅有指令需要传递,我才用了一个阻塞队列来实现指令队列。利用java库中自带的同步容器类保证线程安全。

在调度器线程与电梯线程之间,存在两类同步问题:

  1. 为了避免因时间误差而产生正确性错误,我需要在同一时间获取三部电梯的状态,并且能在该时间将指令传递给电梯线程。
  2. 我需要保证电梯的状态变更能在调度器线程需要访问的时候就能被反映出来。

第一类同步问题,因为我采用的是电梯线程内部假时间的策略,所以很容易解决,只需要利用一把调度器线程与电梯线程共享的“运行锁”即可。电梯线程每次主循环开头获取锁,主循环末尾释放锁。当调度器线程希望停止电梯线程时,只需获取该锁即可。同时为了避免饥饿问题,这里我采用了公平锁,在只有两个线程争夺该锁的情况下,性能损失还是可以接受的。

第二类同步问题,因为当调度器线程访问电梯状态时,电梯线程必定停止,不可能更新自身状态,故而仅需确保电梯状态都能反映到内存中,而不是被缓存。故而我大量使用了volatile变量、原子变量实现轻量级的同步。

2.IFTTT时的策略

线程分析

线程划分非常明细、简单:

  1. 触发器线程组
  2. summary线程
  3. detail线程

同步策略

首先考虑触发器线程组,它们之间共享的是被监控的文件,而这份线程安全性被委托给了FileCenter这一线程安全的File类的封装类。

再考虑summary线程、record线程,它们之间没有共享,但各自都和许多触发器线程共享了它们记录的信息。这是典型的“读者-写者”的情况。写者是一堆触发器线程,读者是需要将记录的信息写进文件的summary、record线程。我采用了消息队列的方法保证了写者与读者的同步,将线程安全性委托给了java的同步容器类。

3.第一次出租车时的策略

线程分析

这一次我抛弃了多线程电梯时注重正确性的策略,没有采用假时间策略。故而这里100个出租车不再只有一个线程,而是真正的100个线程。

同时,我注意到对乘车请求的响应、“抢单窗口”的设计非常适合使用服务器模型进行实现。故而我安排了一个线程池,这个线程池中一个线程对应于正在处理的一个乘车请求,称该线程为调度单元线程。

除此之外,就还有一个标配的输入线程。

同步策略

首先,出租车之间共享地图,以及调度单元。

  1. 因为地图在这次作业中是不可变的,故而线程安全性被保证。
  2. 对于调度单元,我采用了消息机制来处理出租车、调度单元之间的同步问题。即两者都具有消息队列,互相传递信息时仅通过消息队列进行。这样虽然降低了性能、正确性,但简化了实现逻辑。

其次需要保证出租车的状态对其他线程都可见,这一点通过简单的内部锁即可实现。

最后因为需要通过位置、状态来访问出租车,故而我安排了一个缓冲用的TaxisMonitor,出租车监控类来存储缓存信息。因为该缓冲对象会被所有出租车访问来更新缓存,故而需要进行同步。这里我采取了细粒度加锁策略,毕竟本身就是为了性能而做的缓冲,不能因为加锁反而损失性能。对于每一个位置上一个锁,每一种状态上一把锁。

不过因为每一次出租车状态更新会需要访问前后两个状态,如果同时获取两个状态的锁,会导致死锁问题。我的解决方法是让程序同一时间要么只获取前一状态的锁,要么只获取后一状态的锁,虽然会导致出租车在一段时间内在缓冲区中不可见,但可以简单解决了死锁问题。而不可见导致的正确性损失,在这次作业中并无伤大雅。如果真的会因为这点时间的不可见而产生正确性错误,那出租车线程本身运行的时候就会因为过卡而导致走一条边超过200s了。

度量分析

电梯

这次电梯作业光看度量的面板数值还可以,也就输入处理那里我图省事嵌套多了点。但是实际上因为是多次更迭的项目,其中有众多冗余的代码。这点从55个类、2898行代码中就可以看出。

IFTTT

从面板数值上可以看出,这次IFTTT作业最大的问题就是,它的分支判断相当地庞大。这一点我实在想不出怎么避免,我已经将分支判断尽可能封装在一个方法中,并且保证该方法的接口统一性。或许可以采用将分支判断数据化,然后编写自动进行分支判断的代码来解决。

出租车

状态变化——这是这次出租车的红点。我在思考能否通过将状态本身也给抽象出来,作为一个类对待?然后状态变化的逻辑交由状态本身来处理?或许这样就能将复杂的圈逻辑降维,分散到各个状态类中去。

类分析

电梯

从LiftsThread以右下的那一部分代码全部都是和单线程时一致的,也就是电梯内部依然是按照单线程时的运作模式进行运作,不再赘述其内部实现。

为了能够复用单线程时的代码,我在LiftsThread内部,将系统时间的流逝转化成了对Lift响应模拟时间变化的调用次数。也就是每过几几秒就调用一次Lift一个时间粒度的变化函数。

所以实际上,LiftsThread仅仅只是一个用来封装模拟时间的线程,其内部不包含任何调度逻辑。

实际的调度逻辑全部被包含在Schedular及SubSchedular中。其中SubSchedualr为单部电梯时的调用逻辑。Schedular为协调三部电梯的运动量均衡策略逻辑。

SchedularThread仅仅是为了迎合指导书要求而赘写的中介线程。

InputThread负责读入指令,并将其放入CommandTray中。之后SchedularThread从CommandTray中取出指令,再暂停LiftsThread,转交给它指令。

World负责系统内时间的管理。

IFTTT

ifttt中我大量使用了继承,主要原因是指导书的不明确以及来自助教的需求的频繁变更,导致了代码需要不断维护。为了减少代码维护时的工作量,我尽可能地复用代码,减少同质代码的出现。

继承树一共有四支。

  1. Trigger树。Trigger即为触发器,每一个Trigger都是一个实现了Runnable的可运行类,在实际的程序运行中,每个Trigger都是一个监控线程。其工作即为每隔一段时间获取监控对象的快照,随后根据自身响应方法的具体实现,生成Alter,交给注册在自己身上的Task处理。
  2. Task树。Task即为任务。Task负责接收文件快照的变化(Alter),随后根据自身响应方法的具体实现,进行处理。
  3. Recorder树。Recorder负责信息的记录与记录文件的读写。其自身也是一个线程,每隔一段时间就刷新文件中的记录。
  4. Test树。为了方便测试者进行测试,我将较为具体的测试时的运行逻辑封装在了抽象基类Test类中。每一个具体实现了Test类的类都可以作为一个测试样例执行。

FileCenter即为一个线程安全的File类的封装类,负责文件读写的底层封装。

出租车

出租车的类设计主要分为了三个族:

  1. Taxi族。这部分包含了Taxi, Driver, TaxisMonitor。这一族内部高度耦合,三位一体地实现了出租车。

    1. TaxisMonitor负责出租车状态的对外查询,其内部实现为缓冲实现。每当出租车状态发生更新时就会调用该对象,进行缓冲更新。
    2. Taxi负责出租车的运动逻辑的维护。也就是出租车实际在地图上如何位移的问题。
    3. Driver负责出租车的服务逻辑的维护。也就是出租车如何与调度单元进行消息交互的问题。

    Timepasser为Taxi的基类,封装了Taxi的线程逻辑。负责将系统时间的流逝转化成Taxi内部时间的每时间粒度的流逝。

  2. Schedule族。这部分包含了ScheduleServer, ScheduleUnit。ScheduleServer维护了一个线程池,该线程池中每一个线程都为ScheduleUnit的实例化。这一族负责响应乘车请求,并对每个乘车请求分配一个线程进行抢单、分单等逻辑。
  3. Map族。这部分包含了Map, RouteSolver。这一族封装了地图的具体实现。

除了这三族以外,还有几个类是为了之后的扩展而额外实现的:

  • TwoSideMessage。给消息增加了一层抽象,使得之后还能扩展出除了乘车请求交互时的消息以外的消息。
  • Mesable。该接口是为了使得之后其他类也可能能够收发消息而实现的。
  • InputProcessor。该类将输入处理与乘车请求响应分离开来,是为了之后增加除了乘车请求的输入。

设计分析

关于这个,我难以进行分析……因为我不知道我有没有做到。究竟做到什么程度才能算是做到了某一项原则?我没有足够的经验去回答这个问题……我只能说我尽量去做了。

bug分析

主要的bug来自于对指导书理解有误以及未及时看issue和微信群。其次的bug大多是因为我没写正则去检查输入是否合法,只要输入错了,那我就挑正确的然后继续跑,跑不下去再跑错,但是公测要求不管能不能继续跑都要报错。

我没怎么查别人的bug,没那个时间,仅仅只是按照公测要求完成了课程安排的工作。

心得与体会

其实多线程的同步控制并不困难,与电梯复杂的调度逻辑相比,那是很简单的东西。代码编写过程中最大的难点其实是两点:

什么是正确的代码?

我很想说我知道这个问题的答案,但我做不到。仅仅只是阅读指导书,进行需求分析,并不能真正导向“正确的代码”,而只是能接近。在知道这件事情以后,我所能做的或许仅仅只是留出“将代码改正确的余地”。

性能 || 简单

在程序写完前我无从得知程序的性能如何——这很显然,但是我却必须要写完它。如果我在编写过程中就考虑性能问题,那很有可能就会导致我代码根本写不完,或者越写越错。因为优化性能的代码逻辑往往是复杂的。在第一次编写的过程中我往往会采取最简单的那种方法,而不是最快的方法。我所能做的或许仅仅只是留出“将代码变快的余地”。

总结

上述两个问题最终都导向一点:余地。也就是代码的可修改空间、可拓展空间。当我编写的程序不只是写完就行,过了OJ就行的时候,“余地”成了我编写代码时需要重点考虑的因素。

对课程的看法

在电梯的时候,我十分执拗于程序的正确性,通过不断地发issue明确指导书的意思,我虽然不能完全做到,但是却能向“正确的程序”接近。

不过在ifttt时,需求频繁的变更、指导书的不明确让我放弃了对正确性的执拗——那是一件做不到的事情了,同样一份代码,两个人看,一个人觉得对,另一个觉得错,不再有统一规定。

公测逐渐转为互测,互测时bug树可以用一个bug挂满,许多人的公测因为对方没测而满分,吐槽版各种认领代码,等等现象,让我明白了一件事情:这课的成绩没有很大的意义,就和我的博雅课程的成绩的意义差不多。虽然这课的成绩会算进GPA,但是这课的设计就不是以给学生成绩为核心的。

言尽于此,并不是抱怨,这就是我所见到的,我所想的。

原文地址:https://www.cnblogs.com/supplient/p/8973141.html

时间: 2024-10-29 13:40:52

OO第二次博客的相关文章

渐入OO课的深处,探索多线程的秘密——OO第二次博客总结

一次又一次的挑战,一次又一次全新的知识,我来到了多线程的面前 第五次作业 1.度量分析 >第五次作业由于很大程度上调用的是前两次电梯的一些代码,所以存在的问题与前几次也十分相似.同时由于第一次使用多线程来解决问题,可能将某些功能过于集中的放在了个别类中.导致McCabe Cyclomatic Complexity以及Nested Block Depth出现标红的现象. 2.类图 >这次在类图上面问题体现的也很明显,在方法的分配上并没有做的很平均.这主要是由于为第一次多线程作业,所以将大部分的功

OO第二次博客作业(2018春)

写在前面 多线程 搞死人 第五次作业 类图: 这次作业的调度器(als_3_controller)继承了上一次的调度器(smart_controller),并且分解了之前的schduel方法,内容分布在run()和新建的类ELE_thread的isCarry()和run()里.在程序运行启动了类ELE_thread 的三个线程,分别代表三部电梯的控制系统,分别操作三部电梯. 度量分析: 问题都出在ELE_thread 的run()里,包括圈复杂度和嵌套深度.主要是因为把上一次的大部分代码都集中放

OO第二次博客作业

一.第五次作业--多线程电梯 (1)分析: 因为时间比较紧迫,所以采用了伪多线程的方式,即计算还是单线程,但是输出是三个多线程.不过最后被判无效了,GG. 现在分析一下觉得还是挺清晰的,电梯开三个线程,在分派任务的时候wait,notify一下就行了. 算法分析: 1.每个电梯有一个list队列,在新任务来的时候决定加入哪个list 2.有变动的list更新(用上一次的代码,从头算到尾,得到应该输出的真实时间) 3.三个线程死循环,遍历对应的list,如果有请求的应输出时间小于当前时间,输出,标

第二版博客首页安装代码

css: #profile_block{text-align:center;position:absolute; top:60px; right:10px;} #blog-calendar{border-radius: 7px;background:#fff;} #p_b_follow{padding-top:10px;} #p_b_follow a{display:block;width:70px;height:35px; line-height:35px;mrgin-top:10px; te

第二周博客记录 11月27日

11月27日.周一 例会记录: 今天进行项目任务分配和初始讨论. 首先由雷毅同学进行web项目介绍,项目来源于之前参与高级软件开发工具课程设计,所设计的是一个学生信息管理系统.(有学校logo,下面是主功能导航条,左侧是某个选定功能和其子功能的列表,右侧是主窗口).系统的主功能分类,每个功能的子功能自定义,数据库表自定义). 其次进行了项目任务预估,预估项目点有8点,依据最后提交文档内容进行划分,包括5份文档,2份工程源码和可执行文件,1份视频制作. 最后进行了项目分工,分工内容以及理由如下:

学java的第二篇博客

由于在培训课上老师演示的第一个程序是用记事本编辑,DOS命令行输出,所以了解了几个简单的DOS命令行代码: -dir(directory): 列出当前目录下文件及文件夹 -cd(change directory): 进入指定目录 -md(make directory): 创建目录 -rd(remove directory):删除目录 -cd..:返回上一级目录 -cd\:返回到根目录 -exit:退出DOS命令行 用记事本编辑好源程序代码后,运行cmd打开DOS命令行,输入javac 文件名.j

第二次博客作业

一.是否需要有代码规范 1.“这些规范都是官僚制度下产生的浪费大家的编程时间.影响人们开发效率, 浪费时间的东西.” 其实好的代码风格自然而然就形成了,比如等号两边的空格和大括号的位置,形成习惯之后并不会浪费时间,反而是写的乱七八糟的话之后的复查会浪费时间. 2.“我是个艺术家,手艺人,我有自己的规范和原则.” 额...再艺术也是个程序员吧...如果说你写的代码只由你来维护的话就算写成梵高的画也没关系,但是代码是永存的,人是会被拍在沙滩上的,还是能让大家读懂的好. 3.“规范不能强求一律,应该允

我的第二条博客!

今天中午我们上了一个软件工程专业导论,首先了解了软件危机,软件危机是指在计算机软件的开发和维护过程中所遇到的一系列严重问题,有七种典型的表现. 产生软件危机的主要因素分为软件本身和人为因素.为了解决软件危机,1968年NATO会议里提出了软件工程的定义,有七种基本的本质特性.软件工程的基本原理有七种.软件工程方法学主要分为传统方法学和面向对象方法学,懂得了类与对象的区别. Javaweb方面 我尝试着编写了自己的第一个web程序,自己尝试着在MyEclipse配置JRE以及集成汤姆猫服务器.

第二篇博客

C语言是一门通用的计算机语言,因为有着良好的跨平台性,有标准的规格,可以在电脑平台.单片机以及超级电脑上编译运行,正因为C语言的广泛应用,它成为了大学计算机相关专业的必修科目,也成了一些其它理工科类专业的选修课,学期结束,避免不了要做C语言程序设计的课程设计,虽然C语言已发展到了C++14,但在大学的基础课上普遍还是学习C语言,今天小编就以学生成绩管理系统为例,介绍下如何来做C语言程序设计的课程设计. 1.工具/原料 电脑,Visual C++6.0 2.方法/步骤 弄清课程设计的目的 之所以要