• Java多线程编程作业总结


    一.多线程知识总结

    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。

    类图关系

    1. 线程类: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(),实现线程同步。
    2. 关于电梯的类,我将电梯类单独作为一个类,只包括电梯的属性和一个方法,然后将电梯的运行单独作为一个类,Elevator_move类,改类包含所有电梯的方法,以及时间的计算。在Elevator_run线程中通过构造方法传入Elevator类,并新构建一个Elevator_move类的对象。
    3. 关于调度类,调度的方法并不在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的使用增长主要时输入的时候需要创建线程的原因。

    类图说明

    这次的类图明显比较乱

    1. 关于线程类,主要有Modified,Path_changed,Renamed,Size_changed,Test_thread五个线程类。前四个线程不会开始就启动,只有输入有效监控对象,以及监控过程才会新建相应的监控线程。然后是Test_thread是用于测试的线程。
    2. 关于监控文件的类,监控文件的时候,如果是监控目录,那么目录下每一个文件改变都会触发监控器。而目录下文件改变后还要接着监控,我实现的方式是,首先构建一个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 ;
          }
      }
    3. 我们将File_all的一个ArrayList传入方法test中,然后就可以调用该方法,获得fileList、也就是存储了所有文件属性的动态数组。
    4. 那么如果两个线程都监控同一文件会不会有资源竞争的问题呢?答案是会的,因为假设线程1是监控A路径下的文件,监控的是Renamed Then recover。线程2监控的也是A路径下的文件,监控是Modified Then details,那么入股哦线程1先运行,那么在线程1还没有recover A路径下的文件的时候,线程2将会得到错误的结果,发现没有文件。我我采用的解决的方法是通过锁住文件的方法,但是只会锁住SIze_changed,和Renamed两个线程中文件的改变,这样监控同一文件的时候,如果有Renamed的情况就会在操作执行完之后才会运行接下来的线程。
    5. 快照的实现,没有仔细阅读指导书,完全按自己想法来,没有快照的概念,只知道需要比较前后两次文件的差异,比较的方法,伪代码如下:
      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的运行大大增加。中间会有一些较小的峰值,是出租车的运行。

    类图分析

    1. 线程类:其实只有Scheduler和Taxi_move,而其中的Input是之前写的,程序并没有用到,忘记改了,也没有删除,然后就被报了一个设计缺陷,…………,这是提交时候的类图,所以Input还在Taxi_move线程是表示出租车运行的线程,100辆出租车也就有100个Taxi_move线程,Taxi_move线程主要是出租车的运行。而Scheduler线程是调度器线程,该线程的创建时在Request类中,每一个有效输入就创建一个调度线程,在3s之内,如果有车接应,那么就完成一次调度,然后通过改变出租车的属性,Taxi_move线程就会让出租车按照一定的方式运行起来。所以出租车是Taxi_move线程和Scheduler线程的竞争资源?也不完全是,因为,调度器不会调度运行状态的车,而Taxi_move只会调度运行状态的车。
    2. 调用GUI,关于怎样将出租车显示在GUI上呢?当然是每次出租车状态改变的时候就刷新出租车的位置啦。
    3. 关于最短路径,与出租车在游荡的时候的随机路径,都在出租车类里面。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;
                  }
              }

       由于是连通图,所以总会有路走的。

    时序图分析

    可以看出各个类之间的关系

    度量分析

    第一次圈复杂度过关了,是不是很高兴,但是这其实是假的。经过多次测试,我发现问题,真实情况是这样的:

    然而原因是,我太着急了,第一张图是还没有完全测试完的情况还没有完全显示出圈复杂度。

    三.第二次作业总结

    关于多线程编程的几点思考

    1. 线程同步:不知道我这种想法对不对,但是从多线程电梯开始,我就是这么想的,一切的资源竞争关系都是生产者与消费者之间的关系。当然也会有同一个类同时扮演两种角色,这时分析起来会比较复杂。
    2. 线程的运行结束,在多线程电梯那里,我就没有处理好电梯的线程的结束问题,因为我们很多情况下用的是while(true)来运行线程,就容易导致这一点,所以必要的标记还是十分重要的。

    心得体会

      老实说,我其实每次作业都是抱着,只要有效就行的想法写的,所以最后写的也不是特别好,这三周真的是十分的艰难啊。第一周的时候想到一句话:“No Pains ,No Gains” 。再咬咬牙吧,第二周的时候又想到一句话“如果有一天,你觉得生活如此的艰难,那也许是这次的收获将十分的巨大”。第二周的时候,第三次作业都快想放弃了,生命要紧啊,后来还是咬咬牙坚持了下来,因为又想到一句话”将来的你,一定会感谢现在如此努力的自己“。在这十分艰难的日子里,我也只能靠这些鸡汤度日了。所以想和大家共勉吧,来 ”干了这碗鸡汤“。

  • 相关阅读:
    组织过程资产
    事业环境因素
    一起来学习Android自定义控件2-简单的写字板控件
    Android自定义控件1
    一起来学习Android自定义控件1
    Java你可能不知道的事(3)HashMap
    Java你可能不知道的事(3)HashMap
    Java你可能不知道的事(3)HashMap
    java你可能不知道的事(2)--堆和栈
    java你可能不知道的事(2)--堆和栈
  • 原文地址:https://www.cnblogs.com/wevolf/p/8968897.html
Copyright © 2020-2023  润新知