软件构造实验六报告

目录

 

1 实验目标概述···· 1

2 实验环境配置···· 1

3 实验过程···· 1

3.1 ADT设计方案··· 1

3.2 Monkey线程的run()的执行流程图···· 1

3.3 至少两种“梯子选择”策略的设计与实现方案···· 2

3.3.1 策略1· 2

3.3.2 策略2· 2

3.3.3 策略3(可选)···· 2

3.4 “猴子生成器”MonkeyGenerator 2

3.5 如何确保threadsafe?···· 2

3.6 系统吞吐率和公平性的度量方案···· 2

3.7 输出方案设计···· 2

3.8 猴子过河模拟器v1· 2

3.8.1 参数如何初始化···· 2

3.8.2 使用Strategy模式为每只猴子随机选择决策策略··· 2

3.9 猴子过河模拟器v2· 2

3.9.1 对比分析:固定其他参数,选择不同的决策策略···· 3

3.9.2 对比分析:变化某个参数,固定其他参数···· 3

3.9.3 分析:吞吐率是否与各参数/决策策略有相关性?···· 3

3.9.4 压力测试结果与分析···· 3

4 实验进度记录···· 3

5 实验过程中遇到的困难与解决途径···· 3

6 实验过程中收获的经验、教训、感想··· 4

6.1 实验过程中收获的经验和教训··· 4

6.2 针对以下方面的感受···· 4

 

 

 

 

  1. 实验目标概述

本次实验训练学生的并行编程的基本能力,特别是 Java 多线程编程的能力。

根据一个具体需求,开发两个版本的模拟器,仔细选择保证线程安全(threadsafe)

的构造策略并在代码中加以实现,通过实际数据模拟,测试程序是否是线程安全

的。另外,训练学生如何在 threadsafe 和性能之间寻求较优的折中,为此计算吞

吐率和公平性等性能指标,并做仿真实验。

⚫ Java 多线程编程

⚫ 面向线程安全的 ADT 设计策略选择、文档化

⚫ 模拟仿真实验与对比分析

  1. 实验环境配置

简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。

特别是要记录配置过程中遇到的问题和困难,以及如何解决的。

实验环境设置请参见 Lab-0 实验指南。

本次实验在 GitHub Classroom 中的 URL 地址为:

https://github.com/ComputerScienceHIT/Lab6-1170300724

  1. 实验过程

请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。

    1. ADT设计方案

新建了一个包,里面放adt类。

设计了三个类,其中Monkey中包含一个内部类。

 

Kuan.java

对num有设置和访问的操作,代表的是梯子上的板子。

 

Ladder.java

每个梯子中,有各自的板子list,方法中有对自己板子的初始化,主要是给板子加上编号,方法主要分为两种,一种是判断是否有猴子在板子的特殊位置,另一种是获取各种属性,整体长度,第几个板子,和这个梯子的编号。

判断位置包括是否有猴子,是否有向某个方向的猴子,是否在开始位置,也就是第一个板子和最后一个板子是否有猴子。还有一个方法是设置梯子的编号。

里面有整个梯子的list,方便调用各个梯子。

 

Monkey.java

猴子类,包含了内部类state,和速度,过河策略的编号。方法中有开始猴子过河线程,初始化猴子(设置猴子id,猴子速度,猴子方向),在策略方案给出梯子之后上梯子的初始位置,去到下一个位置(同时判断前面是否有猴子,具体能到哪里),输出猴子的状态(主要是写日志)。

内部类MonkeyState

记录出生时间(好像没用上),生存时间,id,在哪个位置上(梯子的哪块板子上),这里的state是过河状态(1表示在岸上,2表示在梯子上,3表示到达对岸),方法有各种设置和访问的函数,还有增加生存时间和更改过河状态。

具体说一下开始猴子线程和上梯子和下一步

public void startCrossRiverThread()

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();

    service.scheduleAtFixedRate

利用service实现线程开始。

先对猴子的过河状态进行判断,2和3比较简单,2就是下一步然后增加生存时间和写日志,3就是结束,写日志。1如下(省略写日志的地方)

else if (state.getstate() == 1) {

        if (this.getcrossStrategy() == 1) {

          Strategy1 stra = new Strategy1();

          stra.setdirection(this.state.direction);

          while (stra.getresult() == 0) {

            stra.run();

            // 正在左(右)岸等待,离出生已?

                     }

            if (stra.getresult() == 0) {

              state.live();

            }

            this.Jump2Ladder(stra.getresult());

        }

      }

判断猴子的策略是多少,然后用策略给出该上哪个板子,其中策略1需要猴子的方向,所以需要先把方向传进去。如果策略的结果是0,说明没有找到合适的板子,需要等待,等待的1秒写在了策略里,所以只需要判断是否为0即可。获得板子之后就可以调用上板子的函数了。

public void Jump2Ladder(int labberm)

synchronized (Ladder.Ladderall)利用锁保证线程安全

上板子先判断猴子走的方向,不同方向上的板子初始位置不同,给出一个方向的代码(省略写日志)

if (this.state.getdirection() == "LR") {

        while (Ladder.Ladderall.get(labberm - 1).fmonkeyonRL()) {

          try {

            Ladder.Ladderall.wait(1000);

          } catch (InterruptedException e) {

            e.printStackTrace();

          }

          Strategy1 stra = new Strategy1();

          stra.setdirection(this.state.direction);

          while (stra.getresult() == 0) {

            stra.run();

            if (stra.getresult() == 0) {

              state.live();

            }

            labberm = stra.getresult();

          }

        }

        this.state.setLocation(Ladder.Ladderall.get(labberm - 1),

            Ladder.Ladderall.get(labberm - 1).getKuanLR(1));

        this.state.nextstate();

      }

Labberm是要上的板子的编号,为了保证安全,所以在上板子的地方又加了一次判断,保证不会出现死锁状况。(之后又进行了修改,增加了判断条件,还防止了起始位置有许多猴子的状况,但是太长了不适合粘贴上来)

 

public void nextStep()

synchronized (monkeylist)保证线程安全

还是,两种方向的写法差不多,只给一边的没日志的代码

Ladder l = new Ladder();

        l = this.state.getladder();

        for (i = 1; i <= l.getKuanlength(); i++) {

          if (l.getKuanLR(i) == this.state.getkuan()) {

            break;

          }

        }

        // 1 2 3 4 5 6 7 8 9 10

        if (this.state.getdirection() == "LR") {

 

          for (int c = 0; c < MonkeyGenerator.monkeyall.size(); c++) {

            if (MonkeyGenerator.monkeyall.get(c).state.getladder() == this.state.getladder()) {

              monkeylist.add(MonkeyGenerator.monkeyall.get(c));

            }

          }

          monkeylist.remove(this);

          for (int x = 1; x <= this.v; x++) {

            for (int c = 0; c < monkeylist.size(); c++) {

              if (monkeylist.get(c).state.getkuan().getnum() == this.state.getkuan().getnum() + x) {

                this.state.setLocation(l, l.getKuanLR(i + x - 1));

                flag = 0;

                break;

              }

            }

            if (flag == 0) {

              break;

            }

          }

          if (flag == 1) {

            this.state.setLocation(l, l.getKuanLR(i + this.v));

          }

          if (this.state.getkuan().getnum() > Ladder.hlength) {

            this.state.nextstate();

            Ladder ladder = new Ladder();

            ladder.setLaddernum(-1);

            this.state.setLocation(ladder, this.state.getkuan());

          }

          monkeylist.clear();

        }

首先找到这个猴子现在在哪(其实不用遍历,因为内部类有这个方法,但是忘了,也懒得改了),知道方向之后,找这个梯子上,在他前面的猴子在哪,根据猴子的速度,如果前面有猴子会挡着它(在他速度覆盖的板子之中),就会到这个猴子未行动之前的位置的后面一个板子上。函数有利用锁每秒运行的方法。不写出来了。

    1. Monkey线程的run()的执行流程图

    1. 至少两种“梯子选择”策略的设计与实现方案
      1. 策略1

优先选择没有猴子的梯子,若所有梯子上都有猴子,则优先

选择没有与我对向而行的猴子的梯子;若满足该条件的梯子有很多,

则随机选择;

采用了指导书的方案2,完成了。代码太长了,写一部分不重复的。

synchronized (Ladder.Ladderall) {

      for (int i = 0; i < Ladder.Ladderall.size(); i++) {

        if (!Ladder.Ladderall.get(i).fmonkeyon()) {

          list.add(Ladder.Ladderall.get(i));

        }

      }

      if (list.size() == 0 && this.result == 0) {

        if (direction == "LR") {

          for (int k = 0; k < Ladder.Ladderall.size(); k++) {

            if (Ladder.Ladderall.get(k).fmonkeyonLR()) {

              list2.add(Ladder.Ladderall.get(k));

            }

          }

          int temp = list2.size();

          if (temp == 0) {

 

          } else {

            int tempm = (int) (Math.random() * (temp));

            this.result = list2.get(tempm).getLaddernum();

          }

        } else {

          for (int k = 0; k < Ladder.Ladderall.size(); k++) {

            if (Ladder.Ladderall.get(k).fmonkeyonRL()) {

              list2.add(Ladder.Ladderall.get(k));

            }

          }

          int temp = list2.size();

          if (temp == 0) {

 

          } else {

            int tempm = (int) (Math.random() * (temp));

            this.result = list2.get(tempm).getLaddernum();

          }

        }

这是最长的一部分,list是一个包含着空梯子的表,把当时的空梯子放进来,根据这个list的大小,来给出不同的结果,这部分是没有空梯子的情况,没有空梯子就用list2,找到同向的梯子,然后随机上一个同向的梯子,如果没找到结果,要等待1秒后再进行判断。

如果list大小只有1,那就返回这个梯子编号就行

如果list大小大于1,就随机选择一个在里面的梯子编号返回。

if (this.result == 0) {

        try {

          Ladder.Ladderall.wait(1000);

        } catch (InterruptedException e) {

          // TODO 自动生成的 catch

          e.printStackTrace();

        }

      }

以防万一在最后加了判断,如果这个函数运行一遍还没有找到,就等待1秒后再找。

 

      1. 策略2

优先选择没有猴子的梯子,若所有梯子上都有猴子,则在岸

边等待,直到某个梯子空闲出来;

  public void run() {

    List<Ladder> list = new ArrayList<Ladder>();

    synchronized (Ladder.Ladderall) {

      for (int i = 0; i < Ladder.Ladderall.size(); i++) {

        if (!Ladder.Ladderall.get(i).fmonkeyon()) {

          list.add(Ladder.Ladderall.get(i));

        }

      }

      if (list.size() == 0) {

        this.result = 0;

      } else {

        int temp = list.size();

        int tempm = (int) (Math.random() * (temp));

        this.result = list.get(tempm).getLaddernum();

      }

      if (this.result == 0) {

        try {

          Ladder.Ladderall.wait(1000);

        } catch (InterruptedException e) {

          // TODO 自动生成的 catch

          e.printStackTrace();

        }

      }

    }

  }

还是利用list的大小来判断是否有空梯子

 

    1.  “猴子生成器”MonkeyGenerator

public void run() {

    for (int i = 0; i < K; i++) {

      Monkey monkey = new Monkey();

      monkey.state.setid(id);

      id++;

      temp = (int) (Math.random() * (1 + 1));

      if (temp == 0) {

        monkey.state.setdirection("LR");

      } else {

        monkey.state.setdirection("RL");

      }

      temp = (int) (1 + Math.random() * (mv - 1 + 1));

      monkey.setv(temp);

      temp = (int) (1 + Math.random() * (3 - 1 + 1));

      monkey.setcrossStrategy(1);

      monkey.state.nextstate();

      monkeyall.add(monkey);

      monkey.startCrossRiverThread();

 

    }

这里是单一策略的猴子生成器,就是new一个猴子加到整体猴子列表里面。

    Timer timer3 = new Timer();

    timer3.schedule(new TimerTask() {

      public void run() {

        monkeyGenerator.run();

        if (MonkeyGenerator.monkeyall.size() >= N) {

          timer3.cancel();

        }

      }

    }, 0, 1000);

在主函数这样调用,实现每秒生成k个猴子的功能

    1. 如何确保threadsafe?

在不安全的线程中加入了锁,在adt里面说了

在上梯子时多加了一个判断,避免出现不知道为什么的bug

if (this.state.getdirection() == "LR") {

        while (Ladder.Ladderall.get(labberm - 1).fmonkeyonRL() ||Ladder.Ladderall.get(labberm - 1).fmonkeyonLRB() ) {

          try {

            Ladder.Ladderall.wait(1000);

          } catch (InterruptedException e) {

            e.printStackTrace();

          }

          Strategy1 stra = new Strategy1();

          stra.setdirection(this.state.direction);

          while (stra.getresult() == 0) {

            stra.run();

            if (stra.getresult() == 0) {

              state.live();

            }

            labberm = stra.getresult();

          }

        }

    1. 系统吞吐率和公平性的度量方案

Timer lol = new Timer();

    lol.schedule(new TimerTask() {

      public void run() {

        int flag = 1;

        if (MonkeyGenerator.monkeyall.size() >= N - 1) {

          for (int i = 0; i < MonkeyGenerator.monkeyall.size(); i++) {

            flag = 1;

            if (MonkeyGenerator.monkeyall.get(i).state.getstate() != 3) {

              flag = 0;

              break;

            }

          }

          if (flag == 1) {

            long endTime = System.currentTimeMillis(); // 获取结束时间

            System.out.println((double) N / ((double) (endTime - startTime) / 1000));

            double temp = 0;

            for(int i = 0 ; i<MonkeyGenerator.monkeyall.size() ; i++)

            {

              for(int k = i + 1 ;k<MonkeyGenerator.monkeyall.size();k++)

              {

                if((MonkeyGenerator.monkeyall.get(k).state.getbeartime() - MonkeyGenerator.monkeyall.get(i).state.getbeartime() ) * (MonkeyGenerator.monkeyall.get(k).state.getlifetime() - MonkeyGenerator.monkeyall.get(i).state.getlifetime()) >=0) {

                  temp = temp + 1;

                }

                else {

                  temp = temp -1;

                }

              }

            }

            double tempp = 1;

            tempp = N * (N-1) / 2;

            System.out.println(temp/tempp);

            lol.cancel();

          }

 

        }

      }

    }, 20000, 1000);

利用timer给吞吐率和公平值一个启动时间,尽量别让他们一直在那判断,20s只是针对了一个情况,可以更改。利用指导书上的算法可以很容易的写出计算的代码。

    1. 输出方案设计

日志:

使用了自己设计一个log类来实现类似log的方法。

自己写的log可以存储String,打印,和写入一个文件之中。

Log.txt写在了根目录里面

 

GUI:

利用了table生成梯子,虽然难看了一点,但是还凑合能用。

刷新部分是查的网上的方法,就是重写repaint函数,把gui的函数整个复制进去就行,然后在主函数里面利用timer不停的每秒调用一次。为了方便看,没有设置自动关闭。

public Guig() {

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    setBounds(100, 100, 1500, 1500);

    contentPane = new JPanel();

    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));

    contentPane.setLayout(new BorderLayout(0, 0));

    setContentPane(contentPane);

 

    JPanel panel = new JPanel();

    contentPane.add(panel, BorderLayout.CENTER);

    String[] columnNames = new String[Ladder.hlength];

    for (int i = 0; i < Ladder.hlength; i++) {

      columnNames[i] = " ";

    }

    Object[][] obj = new Object[Ladder.Laddern][Ladder.hlength];

    for (int i = 0; i < MonkeyGenerator.monkeyall.size(); i++) {

      if (MonkeyGenerator.monkeyall.get(i).state.getladder().getLaddernum() != -1

          && MonkeyGenerator.monkeyall.get(i).state.getladder().getLaddernum() != 0) {

        int templ = MonkeyGenerator.monkeyall.get(i).state.getladder().getLaddernum();

        int temph = MonkeyGenerator.monkeyall.get(i).state.getkuan().getnum();

        obj[templ - 1][temph - 1] = "█" + MonkeyGenerator.monkeyall.get(i).state.getid();

      }

    }

    JTable table = new JTable(obj, columnNames);

    TableColumn column = null;

    int colunms = table.getColumnCount();

    for (int i = 0; i < colunms; i++) {

      column = table.getColumnModel().getColumn(i);

      column.setPreferredWidth(50);

    }

    table.setRowHeight(50);

    panel.add(table);

 

  }

 

    1. 猴子过河模拟器v1
      1. 参数如何初始化

利用了设置文件,在根目录下创建了一个In.txt,在里面写上配制的参数(只能配制指导书上面的几个)格式如下

每行写一个,不能有空格

      1. 使用Strategy模式为每只猴子选择决策策略

在adt中和猴子生成器说过了,随机一个数,根据数字来选择策略

 

    1. 猴子过河模拟器v2

在不同参数设置和不同“梯子选择”模式下的“吞吐率”和“公平性”实验结果及其对比分析。

      1. 对比分析:固定其他参数,选择不同的决策策略

策略1明显比策略2快,

策略1:

策略2:

      1. 对比分析:变化某个参数,固定其他参数

改变n:

n=5:

n=10:

 

改变t和k差不多,就改一个吧:

t=1

t=5

 

改变MV

Mv = 5

Mv=15

      1. 分析:吞吐率是否与各参数/决策策略有相关性?

有关系有关系,最大速度肯定是有关系的,速度越快吞吐率相对就大,梯子多,可以上的猴子就多,吞吐率也相对就大,生成器生成的速度太慢的话,吞吐率肯定就小。

策略上来说,1,2策略相差肯定比较明显,肯定是更好的策略吞吐率越大。

      1. 压力测试结果与分析

结果在上面,分析也在上面

    1. 猴子过河模拟器v3

针对教师提供的三个文本文件,分别进行多次模拟,记录模拟结果。

 

吞吐率

公平性

Competiton_1.txt

 

 

第1次模拟

2.9235206985265454

0.22831661092530658

第2次模拟

2.7726432532347505

0.24749163879598662

第3次模拟

3.0137426665595113

0.29926421404682274

第4次模拟

2.381103562130929

0.0611371237458194

第5次模拟

2.8790786948176583

0.18581939799331104

第6次模拟

2.748460861917326

0.19304347826086957

第7次模拟

2.9235206985265454

0.3435897435897436

第8次模拟

2.8766492789199143

0.22363433667781493

第9次模拟

3.0930386011217417

0.39852842809364547

第10次模拟

2.9232927970065483

0.2923076923076923

平均值

2.853505

0.247313

Competiton_2.txt

 

 

第1次模拟

4.835964097802538

0.11092585170340681

第2次模拟

4.908119993717606

-0.035559118236472946

第3次模拟

4.952064020283654

-0.0023567134268537076

第4次模拟

5.028562233486202

0.06678957915831664

第5次模拟

5.030990903968446

-0.062364729458917835

第6次模拟

5.029776275551264

0.06678957915831664

第7次模拟

4.991215460789011

-0.009811623246492986

第8次模拟

4.948927072610658

-0.0631503006012024

第9次模拟

4.989621587098834

-0.01769939879759519

第10次模拟

5.113729340533465

-0.050180360721442885

平均值

4.982897

0.000338

Competiton_3.txt

 

 

第1次模拟

1.3882718791648156

0.3236363636363636

第2次模拟

1.2240501370936154

0.05333333333333334

第3次模拟

1.3882718791648156

0.1995959595959596

第4次模拟

1.235299930823204

0.08363636363636363

第5次模拟

1.3579576317218902

0.2694949494949495

第6次模拟

1.2740801141575782

0.05333333333333334

第7次模拟

1.189117199391172

0.07474747474747474

第8次模拟

1.1147774904129137

0.05333333333333334

第9次模拟

1.3878094815143778

0.298989898989899

第10次模拟

1.3882718791648156

0.26464646464646463

平均值

1.294791

0.167475

  1. 实验进度记录

请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。

每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。

不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。

日期

时间段

计划任务

实际完成情况

2019.6.10

全体

V1

完成

2019.6.12

全天

V2

完成

2019.6.16

全天

V3

完成

  1. 实验过程中遇到的困难与解决途径

遇到的难点

解决途径

GUI不会刷新

 

 

问了一下同学说是要重写repaint

猴子经常不听话

 

 

好多for循环忘了加break导致出现问题

  1. 实验过程中收获的经验、教训、感想
    1. 实验过程中收获的经验和教训
    2. 针对以下方面的感受
  1. 多线程程序比单线程程序复杂在哪里?你是否能体验到多线程程序在性能方面的改善?

多线程不好保证判断条件,可能会出现这个线程在做判断的时候,另外一个线程可能动了,然后导致判断条件出现问题。多线程程序可以同时运行多个函数,比以往的从上至下运行好多了。

  1. 你采用了什么设计决策来保证threadsafe?如何做到在threadsafe和性能之间很好的折中?

锁,和额外的判断次数,虽然牺牲了性能,但是如果线程不安全那性能就等于0,所以我觉得优先保证线程安全是最重要的。

  1. 你在完成本实验过程中是否遇到过线程不安全的情况?你是如何改进的?

遇到了,感觉锁没有那么好使,所以多加了一次判断是否正确的情况

  1. 关于本实验的工作量、难度、deadline。

都挺好,时间也不紧。

  1. 到此为止你对《软件构造》课程的意见和建议。

木了,反正最后一次了。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值