2.简答题
以下给出了某ADT的一部分代码,请判断其是否threadsafe。若非,请将其修改为threadsafe的代码。
答题时,请先指出有潜在threadsafe风险的行号或行号范围,然后阐述如何修改以消除每一.项潜 在风险,并尽
可能保证高并发性能。最后以注释的形式撰写改造后的ADT的线程安全策略。
public class Poem {
public String title = new String();
private List<String> originLines = new ArrayList<>();
private final List<String> lines = Collections . synchronizedList(originLines);
public void removeSomeLinesAndUnifyTit1e(String filter) {
Iterator<String> iter = lines . iterator();
while (iter. hasNext()) {
String line = iter.next();
if (line. contains (filter))
iter . remove();
}
title = title. toUpperCase();//String . toUpperCase( )将字符串中的字符变为大写
public String toString() {
StringBuilder sb = new StringBuilder(title + "\n");
for (String s : poem)
sb. append(s + "\n");
return sb. toString();
}
...//这里忽略其他读写rep的方法,但答题时请考虑这些方法可能带来的线程安全风险
}
线程安全问题:
public String title = new String();
中title的public属性可能会导致在多线程使用时,从外部修改其属性值,会得到意想不到的结果,导致程序错误。- 虽然
List<String> lines
使用Col1ections . synchronizedList(originLines);
进行装饰,但是仍然保留了其装饰之前类的引用,因此对其的访问可以绕过 synchronizedList导致线程安全问题 removeSomeLinesAndUnifyTit1e()
和toString()
方法没有加锁,其中的很多操作非原子,可能会导致线程安全问题
修改:
因为两个方法都需要对类中的所有rep进行读写,需要用类本身作为锁
同时将originLines
删除,并使用匿名类的方式构造lines
同时增加线程安全策略:
public class Poem {
//Thread Safe Arguments:
// 这个类是线程安全的因为:
// title 是一个不可变类型且为private,所有对title的读写都是同步的
// lines 类用synchronizedList()装饰,且所有对其的读写也是同步的
private String title = new String();
private final List<String> lines = Collections . synchronizedList(new ArrayList<>());
public synchronized void removeSomeLinesAndUnifyTit1e(String filter) {
Iterator<String> iter = lines.iterator();
while (iter. hasNext()) {
String line = iter.next();
if (line. contains (filter))
iter.remove();
}
title = title. toUpperCase();//String . toUpperCase( )将字符串中的字符变为大写
}
public synchronized String toString() {
StringBuilder sb = new StringBuilder(title + "\n");
for (String s : poem)
sb. append(s + "\n");
return sb. toString();
}
...
//这里忽略其他读写rep的方法,但答题时请考虑这些方法可能带来的线程安全风险
}
3.综合设计题
背景描述:
●教师给学生发布了 一组论文,要求每个学生选择不少于1篇、不多于4篇论文加以阅读并做报告。
●由于每篇论文只能被1个学生选择,故多个学生选择论文时可能产生冲突。如果某个论文已被选择,则
其他学生不能再选它。如果某个学生已经选择了至少1篇论文,那么他不能再次选其他论文。
●论文分为学位论文Thesis、期刊论文JournalPaper和会议论文ConferencePaper三类。
●学生可 设定自己对论文类型的偏好和待选论文数量的偏好,例如:希望选择JournalPaper 1篇。
设计了一组ADT:论文Paper (及其三个子类型Thesis、JournalPaper,ConferencePaper)、 教师发布的
论文清单PaperList、学生论文选择结果PaperSelection、学生Student。Client.java是客户端程序。
问题1:代码快照图:
画的比较粗糙,可能存在错误
问题2:
(1)阅读PaperList类的代码,找出所有存在“表示泄露”风险的代码行,说明其为何存在风险,并详细说
明如何修改(文字陈述即可,无需给出代码)。
(2) PaperList类的selectPaperByRandom( )
方法(见Paperlist类代码的第15-22行),将某个Student
对象作为参数传递进去,根据该学生希望选择的论文数量和偏好的论文类型,从PaperList中随机选择
符合要求的论文并返回结果List。第17和18行用于判断目前可选的论文数目与学生希望选择
的论文数量之间的关系,目前的代码逻辑是:如果可选论文数目少于学生希望选择的数量,则不再为学生
选择论文,而是直接返回null。但是,返回null并不是一种好的编程风格。为此,增加一个自定义checked
异常NoEnoughPapersException
,当发生该异常时, 程序应在控制台提示输出“学生XX希望选论文X
篇,但目前只有Y篇可选”,并不再为该学生选择任何论文。
( a ) 写出该异常类的定义代码;
( b ) 对Paperlist类的相关代码进行修改,使之可抛出该异常:
( c ) 修改Client.java,捕获并处理该异常。
第一问:
// Mutable class教师发布的一-组论文,以及学生选择的结果
public class PaperList {
private final String teacher;
//教师姓名
private final List<Paper> papers;
//该 教师发布的论文清单
private final PaperSelection selections = new PaperSelection();
//选择论文结果
public PaperList(String teacher, List<Paper> papers) {
this. teacher = teacher;
this. papers = papers;
#未对paper进行防御式拷贝,因此paper可能从外部索引被更改
}
//根据论文类型,到papers中找出尚未被选择的论文,返回清单。type=Any 意味着任何未被他人
//选择的论文都会包含在返回值中
private List<Paper> getUnselectedPapersByType(PaperType type) {
List<Paper> results = new ArrayList<>();
for (Paper p : this . papers) {
if(! this. selections . isSelected(p)) {
if(type == PaperType . Any
|I type.toString().equals(p. getClass(). getName()))
//注:.上方代码的两个 getXX()得到p的具体类名
results.add(p);
}
return Collections . unmodifiableList(results);
}
//根据学生偏好,为其随机选择一组论文
public List<Paper> selectPaperByRandom(Student student) {
List<Paper> availablePapers =
getUnselectedPapersByType(student. getPreferredPaperType());
.
//若可选论文的数量少于学生希望选择的数量,不再继续执行,直接返回null
if(availablePapers.size() < student.getPreferredQuantity())
return null ;
List<Paper> papers = randomSelectPapers( availablePapers ,
student.getPreferredQuantity());
for (Paper p : papers )
selections.addSelection(student, p);
return papers;
}
public PaperSelection getSelectionResult() {
#返回的PaperSelection是一个mutable的类,必须进行防御式拷贝
return this.selections;
}
//从availablePapers论文清单中随机选择q篇论文,返回选择结果
private List<Paper> randomSelectPapers(List<Paper> availablePapers, int q) {
//code ignored here
}
}
第二问:
异常类代码:
public class NoEnoughPapersException() extends Exception{
public NoEnoughPapersException(String name,int x,int y){
super();
System.out.println("学生"+name+"希望选论文"+X+"篇,但目前只有"+Y+"篇可选”,并不再为该学生选择任何论文");
}
}
Paperlist的修改:
public List<Paper> selectPaperByRandom(Student student) {
List<Paper> availablePapers =
getUnselectedPapersByType(student. getPreferredPaperType());
.
//若可选论文的数量少于学生希望选择的数量,不再继续执行,直接返回null
if(availablePapers.size() < student.getPreferredQuantity())
throw new NoEnoughPapersException(student.getName,student.getPreferredQuantity(),availablePapers.size())
List<Paper> papers = randomSelectPapers( availablePapers ,
student.getPreferredQuantity());
for (Paper p : papers )
selections.addSelection(student, p);
return papers;
}
客户端的修改:
try -catch即可
第五题:
子问题1: Paperlist类的selectPaperByRandom()函数目前采用随机选择论文的方案(见第19行调用的
randomSelectPapers()方法),它有两个参数:可选论文清单availablePapers、需要选择的论文数量),返回
一组选定的论文。假如用户希望将来在“随机选择”之外还有其他更灵活的论文选择策略(例如按论文加入列表的
次序从早到晚来选),并可在client端灵活使用某种特定的论文选择策略。为遵循0CP原则,请基于Strategy
设计模式对当前设计进行修改,以最小的修改代价让程序灵活适应各种论文选择策略。
子问题2:考虑到将来对PaperSelection类的扩展,为了遵循0CP原则,使用Visitor设计模式对该类进
;行改造,程序员将来可开发多个新的visitor子类,实现对PaperSelection内部存储数据的各种灵活查询(例
如:查询有多少学生已经选择了至少1篇论文)。请对PaperSelection类进行改造,并试着撰写-一个具体的
visitor类,以查询有多少学生已选择了至少1篇论文。
子问题1:
UML类图:
子问题2:
在PaperSelection类中预留visit()方法:
将处理数据的 功能 delegate到 外部传入的 visitor
public accept(PaperSelectionVisitor psv){
psv.visit(this);
}
构造的接口
public interface PaperSelectionVisitor {
int visit(PaperSelection ps);
}
实现的具体类:
public class ConcentrateVisitor {
//查询有多少学生已选择了至少1篇论文
int visit(PaperSelection ps){
List<Student> stus=new Arraylist();
Map selection=ps.getSelection();
for(Student s:selection.values()){
if(!stus.contains(s))
stus.add(s);
}
return stus.size();
}
第六题
spec:
(1)下面给出了 PaperList类的selectPaperByRandom( )方法的spec,但不完整。请在空白处补充相应的
内容,使用中文和代码中出现的类名、方法名、变量名等。不要自己想象,要基于问题描述进行spec设计,但
无需- .定要遵循该方法已有的代码实现。在做此题时,你务必已经完成了第四题(2)。若你认为该函数需抛出
NoEnoughPapersException之外的其他异常以应对你设计的pre-condition,请自行在下方空白处补充。
@param student:
表示将要选去论文的学生,要求该学生尚未选择论文
@return
返回为学生选择的论文
@throws 如果没有论文可选,抛出NoEnoughPapersException异常
(2)为selectPaperByRandom()设计黑盒测试用例。考虑到该方法的复杂性,先从最基本场景开始测试:针
对一个固定的PaperList对象,仅考虑一个 Student对象。假如你的JUnit测试代码如下所示,1-7 行构建了
一个PaperList对象,第8行构建了一个Student对象,第9-13行进行具体测试。请设计若干Student对象,
逐个替换到第8行的位置,使测试尽可能充分。将测试用例填入下面表格里即可,并简要阐述你的testing
strategy。注:设计测试用例时,需要基于你在(1)中为该函数写出的spec.
@Test
void testSelectPaperByRandom() {
final PaperList listOfRainy = new PaperList("Rainy",
Arrays. asList(
new Thesis("Reading"),
new ConferencePaper ("Science"),
new ournalPaper ("Technology"),
new JournalPaper (" Art")
)
);
final Student student = new Student( "Cloudy", 1, PaperType . Thesis);
try {
List<Paper> papers = listOfRainy.selectPaperByRandom( student);
assertTrue(...);
//开展测试, 判断后置条件@return是否满足
}
catch (NotEnoughPapersException e) {
assertTrue(...);
//开展测试, 判断后置条件@throws是否正确捕捉
}
}
测试策略:
针对一个固定的PaperList对象,仅考虑一个 Student对象
分成一下几个等价类:
针对学生选择的论文种类:
Any
Thesis、JournalPaper,ConferencePaper
针对学生选择论文的数量关系:
论文的总数量不满足学生的需求
论文的总数量不满足学生的需求,但学生选择的论文种类的数量不满足学生的需求
学生选择的论文种类的数量不满足学生的需求
论文的总数量和学生选择的论文种类的数量都满足学生的需求
针对学生选择论文的具体数量:
学生选择0篇论文
学生选择1篇论文
学生选择了全部的论文
按照上面的等价类,构造笛卡尔集即可