一.多线程知识总结
1.线程同步
有关创建线程的知识就不过多的叙述了。就从主要的开始讲吧,讲一下线程的同步。与操作系统中的进程同步一样,线程同样面临着资源共享的问题,怎样处理线程的资源共享是运用多线程最重要的地方。在Java中是引入锁这一概念来处理多线程之间的资源竞争的关系的。“锁”的对象可以是代码块,方法,还可以是对象。一旦某一部分被锁住,我们称该部分获取了锁。那么在java多个线程中,只有拥有锁的线程才可以运行,其他线程不能运行。如果我们将竞争资源的代码块锁起来,就可以避免发生冲突。在运用锁的过程中,通常使用的是synchronized();表示锁住某一部分代码块。那么我们还介绍以下,wait(),和notifyAll(), notify的用法。举个例子,以多线程电梯为例子吧,在多线程电梯中的输入与1调度器之间的关系。我们一次输入同一时刻的多条指令,我们将这多条指令称为临时队列。每次调度每一次输入的指令,将每一次输入的指令加入到总的队列中去。那么就要求输入的时候,不可以运行调度器访问输入的临时队列,因为如果多次访问将造成一次输入被调度多次,而调度的时候也不能写入指令,因为多次写入会导致重写前一次的输入,也就忽略了前一次的输入。看一下代码:
while(true) { while(this.sign!=0) { //表示队列不为空 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } /** 表示中间处理过程 */ sign=1; //用于标记 notifyAll(); }
二.三次作业分析
2.多线程电梯
刚刚接触多线程确实是十分的艰难,刚拿到的时候毫无头绪。最后的成绩也不是很好,不过之后debug之后感觉就好多了。首先看一下JProfiler性能分析的结果。
JProfiler性能分析的结果
用wait()方法锁住的线程表示为红色,在输入阶段CPU的使用也是最多的,然后是三个电梯都接收指令后开始调度。最后输入END后输入线程结束,还有电梯没有执行完。直到执行完后结束。分为三个阶段的化输入的时候也会有调度,所以处理器的使用率最高,然后是三部电梯同时调度,最后是一部电梯调度,可以看出三部电梯同时调度的时候CPU的使用要比单个电梯调度的时候高很多。说明除了运行线程,CPU处理线程之间的调度也占用CPU。
类图关系
- 线程类:Elevator_run; Input; Super_ride。其中Input表示输入线程类,然后是Elevator_run表示电梯运行线程类,所以创建三个Elevator_run线程类,然后是Super_ride线程类,表示调度线程类。线程之间是并行的,但是输入与调度之间的线程必须有wait()和notify()关系,防止对w_legal类中的数据冲突而产生错误。同时,Super_ride类和Elevator_run类之间共享资源Elevator类,所以在Super_ride调用Elevator时Elevator_run不能调用,同理,Elevator_run调用Elevator时Super_ride不能调用,所以在这两个线程中使用synchronized(),实现线程同步。
- 关于电梯的类,我将电梯类单独作为一个类,只包括电梯的属性和一个方法,然后将电梯的运行单独作为一个类,Elevator_move类,改类包含所有电梯的方法,以及时间的计算。在Elevator_run线程中通过构造方法传入Elevator类,并新构建一个Elevator_move类的对象。
- 关于调度类,调度的方法并不在Super_ride中直接实现,同样我也写了一个专门负责调度的类,只包括调度的属性以及方法。调度的时候首先在Super_ride中通过构造方法传入电梯与指令,然后实例化一个调度器,将电梯与临时队列传入调度器,就可以得到每个电梯的队列。
时序图分析
度量分析
3.文件系统
有关IFTTT
https://en.wikipedia.org/wiki/IFTTT,或者,https://baike.baidu.com/item/ifttt/8378533?fr=aladdin,而IFTTT本事也是一款十分强大的app,大家可以去试试,https://ifttt.com/products。
JProfiler性能分析的结果
由于对于这次的测试,测试文件不是很多,所以CPU的使用比较低。从图中可以明显的看出我存在的一个问题,那就是没有一直监控文件。再之后我会说明。一个输入会增加一个线程,也就是说,每一个文件对应一个监控线程,用来实时监控改文件。对于为什么会有线程结束,是因为,再我的程序中,监控文件只会监控一次变化,但是监控目录会监控多次变化。在类图部分会说明原因。开始阶段CPU的使用增长主要时输入的时候需要创建线程的原因。
类图说明
这次的类图明显比较乱
- 关于线程类,主要有Modified,Path_changed,Renamed,Size_changed,Test_thread五个线程类。前四个线程不会开始就启动,只有输入有效监控对象,以及监控过程才会新建相应的监控线程。然后是Test_thread是用于测试的线程。
- 关于监控文件的类,监控文件的时候,如果是监控目录,那么目录下每一个文件改变都会触发监控器。而目录下文件改变后还要接着监控,我实现的方式是,首先构建一个File_all类;
class File_all {protected String name=null; protected long f_l=0; protected long l_t=0; protected String path=null; }
由于File方法返回的是指向文件的指针,那么当路径改变的时候,返回值就会改变,所以,File_all的作用是存入路径改变之前的文件的属性,用于之后的文件改变后用于比较。那么是如何获取整个目录下所有的文件的呢?当然是使用递归搜索的方法。
class Test { protected void test(String fileDir, ArrayList<File_all> fileList) { // = new ArrayList<File_all>(); File file = new File(fileDir); File[] files = file.listFiles();// 获取目录下的所有文件或文件夹 if (files == null) {// 如果目录为空,直接退出 return ; } // 遍历,目录下的所有文件,将文件属性存起来 for (File f : files) { File_all tem_file = new File_all(); if (f.isFile()) { tem_file.file = f; tem_file.name = f.getName(); tem_file.f_l = f.length(); tem_file.l_t = f.lastModified(); fileList.add(tem_file); } else if (f.isDirectory()) { test(f.getAbsolutePath(),fileList); } } return ; } }
- 我们将File_all的一个ArrayList传入方法test中,然后就可以调用该方法,获得fileList、也就是存储了所有文件属性的动态数组。
- 那么如果两个线程都监控同一文件会不会有资源竞争的问题呢?答案是会的,因为假设线程1是监控A路径下的文件,监控的是Renamed Then recover。线程2监控的也是A路径下的文件,监控是Modified Then details,那么入股哦线程1先运行,那么在线程1还没有recover A路径下的文件的时候,线程2将会得到错误的结果,发现没有文件。我我采用的解决的方法是通过锁住文件的方法,但是只会锁住SIze_changed,和Renamed两个线程中文件的改变,这样监控同一文件的时候,如果有Renamed的情况就会在操作执行完之后才会运行接下来的线程。
- 快照的实现,没有仔细阅读指导书,完全按自己想法来,没有快照的概念,只知道需要比较前后两次文件的差异,比较的方法,伪代码如下:
if(file_2.isDirectory()) { chose = 0; ArrayList<File_all> FileList = new ArrayList<File_all>(); //新建FileList存储旧的文件 test_sc.test(obj_path,FileList); //递归获取旧的文件属性,并存储 while(true) { ArrayList<File_all> FileList2 = new ArrayList<File_all>(); //新建新文件 test_sc.test(obj_path,FileList2); //递归获取新的文件属性,并存储 /** 各种操作 */ FileList.clear(); //各种比较操作结束后,也就是将新旧文件比较后,清空新旧文件 FileList2.clear(); test_sc.test(obj_path,FileList); //再一次获得旧文件,存储旧文件,用于之后比较 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
时序图分析
度量分析
度量分析的结果显示,代码还是有漏洞。
4.出租车
JProfiler性能分析的结果
出租车的测试,我采用了一次性输入了大量的请求。从CPU运行的几个峰值来看,第一个是程序开始,创建100个出租车线程,可以看出创建线程是比较占用CPU的,然后是每一次输入,每一次输入是比较占用CPU的,原因是,每一个输入或构建一个调度线程,同样需要创建多个线程,就导致CPU的运行大大增加。中间会有一些较小的峰值,是出租车的运行。
类图分析
- 线程类:其实只有Scheduler和Taxi_move,而其中的Input是之前写的,程序并没有用到,忘记改了,也没有删除,然后就被报了一个设计缺陷,…………,这是提交时候的类图,所以Input还在Taxi_move线程是表示出租车运行的线程,100辆出租车也就有100个Taxi_move线程,Taxi_move线程主要是出租车的运行。而Scheduler线程是调度器线程,该线程的创建时在Request类中,每一个有效输入就创建一个调度线程,在3s之内,如果有车接应,那么就完成一次调度,然后通过改变出租车的属性,Taxi_move线程就会让出租车按照一定的方式运行起来。所以出租车是Taxi_move线程和Scheduler线程的竞争资源?也不完全是,因为,调度器不会调度运行状态的车,而Taxi_move只会调度运行状态的车。
- 调用GUI,关于怎样将出租车显示在GUI上呢?当然是每次出租车状态改变的时候就刷新出租车的位置啦。
- 关于最短路径,与出租车在游荡的时候的随机路径,都在出租车类里面。GUI.java中提供了查找最短路径的方法,也许有部分同学会使用guiInfo类中的distance方法,但是仔细以看,其中D[][],是guiInfo类的一个属性,那么我们就可以直接通过pointbfs(root)方法,获取任意一点到root的最短路径,然后将路径存在D[][]中,这样会大大的减少最短路径的计算时间。采用真实时间的同学,可以这样做,不过在下用的假时间就不必太在乎了。关于随机路径,
while(true) { direction = (int)(1+Math.random()*5); //随机选一个位置 if(direction==1 && this.location_x<79 && map_mess.graph[location][location_1]==1) { this.location_x = this.location_x+1; break; } else if(direction==2 && this.location_x>0 && map_mess.graph[location][location_2]==1) { this.location_x = this.location_x-1; break; } else if(direction==3 && this.location_y>0 && map_mess.graph[location][location_3]==1) { this.location_y = this.location_y-1; break; } else if(direction==4 && this.location_y<79 && map_mess.graph[location][location_4]==1) { this.location_y = this.location_y+1; break; } }
由于是连通图,所以总会有路走的。
时序图分析
可以看出各个类之间的关系
度量分析
第一次圈复杂度过关了,是不是很高兴,但是这其实是假的。经过多次测试,我发现问题,真实情况是这样的:
然而原因是,我太着急了,第一张图是还没有完全测试完的情况还没有完全显示出圈复杂度。
三.第二次作业总结
关于多线程编程的几点思考
- 线程同步:不知道我这种想法对不对,但是从多线程电梯开始,我就是这么想的,一切的资源竞争关系都是生产者与消费者之间的关系。当然也会有同一个类同时扮演两种角色,这时分析起来会比较复杂。
- 线程的运行结束,在多线程电梯那里,我就没有处理好电梯的线程的结束问题,因为我们很多情况下用的是while(true)来运行线程,就容易导致这一点,所以必要的标记还是十分重要的。
心得体会
老实说,我其实每次作业都是抱着,只要有效就行的想法写的,所以最后写的也不是特别好,这三周真的是十分的艰难啊。第一周的时候想到一句话:“No Pains ,No Gains” 。再咬咬牙吧,第二周的时候又想到一句话“如果有一天,你觉得生活如此的艰难,那也许是这次的收获将十分的巨大”。第二周的时候,第三次作业都快想放弃了,生命要紧啊,后来还是咬咬牙坚持了下来,因为又想到一句话”将来的你,一定会感谢现在如此努力的自己“。在这十分艰难的日子里,我也只能靠这些鸡汤度日了。所以想和大家共勉吧,来 ”干了这碗鸡汤“。