软件构造心得(14):从两道期末考试题看待并发的一些思路与技巧(附答案参考)

前言

哈工大的软件构造期末考试题中,最有难度的往往就是多线程了。而很多同学包括我在内,也是上课听的很懂,但是实际操作中出现了很多问题。这篇博文就和大家一起捋捋期末考试题,看看技巧和入手的点在哪里。如有错误恳请指正。另附我们讨论出的陶神佬最终敲定的一份答案,欢迎大家一起来讨论,继续加大复习的深度!


2018年期末考题第二道大题

软件构造心得(14):从两道期末考试题看待并发的一些思路与技巧(附答案参考)_第1张图片

先po一下笔者认为的有风险的地方。

  1. 第一行的public,必然会引发表示泄露,而表示泄露从线程角度来看也是危险的,所以要将其改为private。加不加final修饰呢?我们发现,每一次调用removeSomelinesAndUnifyTitle方法都会进行一次大写的转换,而这个转换必然是会造成重新赋值的,所以不需要加上final修饰词修饰。
  2. 3、4行很明显露出了破绽,这种非匿名的方式把originLines留下来当作引用的风险,万一哪个糊涂蛋接手代码使用了这个那真的就会出带问题。所以两行合并成一行,细节就不说了。
  3. 第6行到第11行都应该被包括在一个锁内,那么用什么来锁,为什么缩到这个范围,可不可以再小呢?这些问题都是好问题。首先我们要明白会引发mutable类型线程不安全的原因就是其rep是可以被改变的,在我们的例子中就是所谓的lines是可以被改变的,因为共享内存的意义就是只要值不改变怎么顺序操作都不会彼此影响,但是数值一旦改变就会出现连锁问题(例子数不胜数)。那么我们就要保护pf,所以我们就最简单的方法,用pf上锁,内部修改没完事谁也别想得到pf锁。然后,范围是哪里呢,我们就要看在那块开始会影响到修改,iter那部分需要包进来吗?需要,因为如果我们不包,拿下来一个iter,另一个线程就有可能进行pf的修改,进而我们这个线程的pf的iterator再循环就会出索引上的问题;那么removw要不要包,那肯定,如果不包进来也是会在要remove之前被其他remove影响。touppercase包不包?如果包了那可真就“达到下限了”,我们发现即使有先后,但是由于touppercase只会被影响一次,而且不会体现在结果上,所以并没有什么问题出现。所以就得到了最终范围。
  4. stringbuilder和stringbuffer可换可不换,最好不换,因为不会造成线程问题,且效率更高。
  5. 16、17行,我们要使用pf锁包起来。有同学可能死脑筋:“老师明明说了只看不改不用加锁的?0-0”,诚然,但是我要说,这就和内卷效应和剧场效应一样,你大家都只看不碰,谁都没问题;一旦有一个人懂了歪心思,那么大家就都做不到线程安全了。这个tostring也是一个道理,假设说我们不加锁,在某一次调用tostring的for循环的某一个位置插进去一个remove操作把pf改掉了,那么很明显我们想得到的就不是我们所期待的tostring了。

本题心得

  1. 关注问题的根源是可变类型的修改,我们也要从被修改的rep进行入手。
  2. 范围是一点点找出来的,而不是一眼猜的。我们从变化rep的语句入手,不断地假想出来最坏的必须要锁的情况,进而一步步的扩大锁的范围。
  3. 剧场效应类似,大家都只看不碰没问题,一旦有一个人动了修改了rep,那无论其他是简单的observer还是什么,大家必须都要跟着加锁使得线程是安全的。要求我们具有整体观和大局观,辩证的看待线程
  4. 局部变量的是安全的,多线程波及不到的。在大脑中一定要形成空间意识,在大脑中就将栈和堆分开

2019年最后一道大题第二问

软件构造心得(14):从两道期末考试题看待并发的一些思路与技巧(附答案参考)_第2张图片值得注意的是他想让我们“指出如何修改ConcreteParkingField、Lot、Car、ParkingThread 的代码”

先po一下笔者认为的有风险的地方。

  1. Lot,Car本身是immutable的,不存在线程危险
  2. 对于ConcreteParkingField,我们首先的保证getOneFreeLot这个东西是安全的。如果这个东西本身不是安全的,那么在上面的代码的第11行就会出现你实际上搞回来一个数字,但是这个数字同时也被别的线程抢走了的情况。而这个方法没有给i相应的具体实现,所以我们只能把这个方法的signatrue加上synchronized。
  3. 回到ParkingThead代码上来,我们看到实际上它的逻辑就是尝试停车,如果没有车位,那么久sleep等待。我们还是按照惯例把切入点放到rep修改上,追踪到了第1行,开始上锁。正确的一种解法就是把第11行道第17行(当前的),都包到锁里面。因为我们要避免先找到一个freelot之后却被另一线程抢停车导致脱离预期的情况,虽然这样有可能会因为sleep锁住很久,但是如果我们选择上锁,就只能这样了。但是这也不是最优解,经涛哥指正,我们再次考虑会发现11行和17行的分离就是一个离谱的事,其实我们应该将11和17合并,即将17放到11下面,然后这两行上一个锁,这样就最佳了

本题总结

1. sleep不会交出来锁。带着锁sleep。
2. 更优化的办法实际上是使用wait和notifyAll,让这个线程wait,然后交出来锁,别的线程停车完事再notify停车,但这个不是我们需要掌握的核心知识。

近两年试题答案参考

感谢王忠杰老师的栽培,感谢刘铭老师的答疑,感谢粉丝群的讨论工作,感谢陶新昊神佬的整理,将近两年哈工大软构的答案整理放在如下链接,粉丝群出品,必属精品:
(陶佬说不让膜他,可是我们必须膜。老铁,我做的对吗?
https://share.weiyun.com/rw2VYlbU

你可能感兴趣的:(笔记,java,软件构造)