作业11-多线程
1. 本周学习总结
1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容。
2. 书面作业
本次PTA作业题集多线程
1. 源代码阅读:多线程程序BounceThread
1.1 BallRunnable类有什么用?为什么代码中需要调用Thread.sleep
进行休眠?
答:BallRunnable
类实现了Runnable
接口,支持了多线程,在其中用于给线程指派任务,在类中的run方法里通过循环调用小球的移动函数move
和重画函数repaint
来实现小球的移动轨迹。还有用了Treep.sleep()
来使线程休眠一段时间。
Tread.sleep
让线程进行休眠是为了延缓线程完成的时间,这样可以让我们看到小球的移动,不然不进行休眠的话,这个程序会在一个很快的时间内完成,没有办法看到小球的移动轨迹,并且sleep()为Thread类的静态方法,调用它不会创建一个新线程。 1.2 Ball.java只做了两件事,这两件事分别是什么?BallComponent对象是干什么的?其内部的ArrayList有什么用?程序运行过程中,生成了几个BallComponent对象?该程序使用了多线程技术,每个小球是分别在不同的线程中进行绘制吗?
答:Ball.java
完成的两件事:
- 定义了
move
函数实现小球的移动方法 - 定义了
getShape
函数来获取小球的大小和坐标
BallComponent
对象也实现了两件事:
- 添加一个小球
- 画出一个小球
其内部的ArrayList用于存放添加的小球
程序中只生成了一个BallComponent对象 通过源代码可以看出每按一次start
按钮,addBall
方法都会启动一个新线程,因此每个小球都是在自己的线程中绘制的。 1.3 选做:程序改写:程序运行时,每个小球都是从固定位置出发。如何改写该程序,使得当点击start时,每个小球可以从不同位置出发、以不同的步进移动?
答:可以使用Math.random()方法将ball.java
中的x
、y
、dx
、dy
的值设置成随机数来达到每次点击start
时使小球以不同位置出发和以不同步前进。
1.4 选做:不同小球的移动轨迹一模一样。改造程序,使得每个小球运行轨迹不完全一样,比如有的可以走余弦轨迹、有的可以走方波轨迹、有的走布朗运动、有的走五角星,等等。
2. 实验总结:题集(多线程)
2.1 题目:Thread、PrintTask、Runnable与匿名内部类。
并回答:a)通过定义Runnable
接口的实现类来实现多线程程序比通过继承自Thread
类实现多线程程序有何好处?b) 6-1,6-3,6-11实验总结。
Runnable
接口来实现多继承的程序的好处: - 1.Java中不支持多继承,一个类可以继承多个接口,但是只能继承一个父类,因此使用实现接口的方法可以避免继承的局限。
- 2.使用
Runnable
接口适合于资源的共享,如下两张图片:
可以看出,虽然两个程序中都创建了两个线程,但是使用Runnable
实现多线程使两个线程一起完成两个任务,达到资源共享的目的。
Thread.currentThread().getName()
,然后就是按要求输出就OK了。PrintTask实验总结 这题……除了类名和最后输出的标识信息,其他都和6-1一样hhhh 2.2 使用Lambda表达式改写6-3
Thread t1 = new Thread(() -> { System.out.println(mainThreadName); System.out.println(Thread.currentThread().getName()); System.out.println(Arrays.toString(Thread.class.getInterfaces())); });
2.3 题目:6-2(Runnable与停止线程)。回答:需要怎样才能正确地停止一个运行中的线程?
答:在Java的Thread类中提供了一个stop()方法用来终止线程,不过,因为这个方法会将执行到一半的线程强行终止,,不能保证线程的资源正确释放,所以已经被废弃了。
现在在我们需要停止一个线程的时候一般使用一个boolean类型的变量来终止线程,这样可以使用while语句,在运行中通过改变boolean标记值来使while循环退出来达到停止线程的作用。 我们还可以使用interrupt
方法来终止线程,但是,这种方法并不适用于一个正在运行的线程,它是用于停止一个受到阻塞的线程,通过使受阻塞的线程抛出异常来退出阻塞的状态。 因此还是使用boolean类型的变量来终止线程的方法比较好。 2.4 选做:6-8(CountDownLatch)实验总结
这题就是跟着注释一步步写下来就可以了。
CountDownLatch
类是一个同步计数器,构造时传入int参数,用于表示任务的个数。这个参数是计数器的初始值。 使用Executors
类的newFixedThreadPool
方法可以创建一个固定线程数的线程池。 2.5 选做:6-9(集合同步问题)实验总结
要想让ArrayList等达到线程安全,只需要用synchronizedList
修饰list即可。synchronizedList
通过对部分操作加上synchronized
来保证线程安全。
2.6 选做:较难:6-10(Callable),并回答为什么有Runnable了还需要Callable?实验总结。
3. 互斥访问
3.1 修改TestUnSynchronizedThread.java源代码使其可以同步访问。(关键代码截图,需出现学号)
其实只要在Counter类的对id进行操作的两个方法前加上synchronized
关键字就可以了,它可以通过给共享资源上一道锁来使在同一个时间内只允许一个线程访问共享资源,以此来实现同步机制。
3.2 选做:进一步使用执行器改进相应代码(关键代码截图,需出现学号)
4. 互斥访问与同步访问
完成题集6-4(互斥访问)与6-5(同步访问)
4.1 除了使用synchronized修饰方法实现互斥同步访问,还有什么办法可以使用synchronized实现互斥同步访问,使用代码说明(请出现相关代码及学号)?
答:还可以通过synchronized
的同步代码块来实现互斥访问。
4.2 同步代码块与同步方法有何区别?
答:同步方法直接在方法名前面加synchonrized
关键字来实现对对象的加锁,而同步代码块则是在方法的内部使用synchonrized
关键字来加锁,因此,同步方法的作用范围比同步代码块大,其性能则不如同步代码块。
4.3 实现互斥访问的原理是什么?请使用对象锁概念并结合相应的代码块进行说明。当程序执行synchronized同步代码块或者同步方法时,线程的状态是怎么变化的?
答:实现互斥访的原理是程序通过给共享资源上一道锁,让给定时间内只允许一个线程来访问共享资源。
下面通过分析一个从书上看到的模拟火车站售票系统的程序来说明同步机制运行时的线程状态,代码如下public class ThreadSafeTest implements Runnable { int num = 10; //设置当前总票数 public void run() { while (true) { synchronized ("") { if (num > 0) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println("tickets" + --num); } } } } public static void main(String[] args) { ThreadSafeTest t = new ThreadSafeTest(); //实例化类对象 Thread tA = new Thread(t); // 以该类对象分别实例化4个线程 Thread tB = new Thread(t); Thread tC = new Thread(t); Thread tD = new Thread(t); tA.start(); // 分别启动进程 tB.start(); tC.start(); tD.start(); }}
当我们运行程序的时候,一个线程先开始工作,给num上了一把锁,然后其他三个线程就不能再访问这个区域,等到tA执行完售票的操作将票数减1并输出后,对象锁被释放,然后下一个线程又开始工作,再给num上了一把锁,以此类推,直到票数为0后结束进程。
4.4 Java多线程中使用什么关键字实现线程之间的通信,进而实现线程的协同工作?
- synchronized 关键字
- wait/notify方法
- volatile关键字
5. 线程间的合作:生产者消费者问题
5.1 运行MyProducerConsumerTest.java。正常运行结果应该是仓库还剩0个货物。多运行几次,观察结果,并回答:结果正常吗?哪里不正常?为什么?
可以看出运行结果不正常,最后还有出现货物有剩余的情况,这是因为Producer和customer的存取速度不一样,导致两者不能很好的进行交互,从而使最后剩于货物结果出现异常。
5.2 使用synchronized, wait, notify
解决该问题(关键代码截图,需出现学号)
运行结果: 5.3 选做:使用Lock与Condition对象解决该问题。
运行结果:
6. 面向对象设计作业-图书馆管理系统
6.1 系统的功能模块表格,表格中体现出每个模块的负责人。
学生 | 负责任务 |
---|---|
陈晓菲 | 图书管理模块 |
廖文姑 | 菜单类及测试类 |
贾海涛 | 用户管理模块 |
6.2 运行视频
6.3 讲解自己负责的模块,并粘贴自己负责模块的关键代码(出现学号及姓名)。
我负责的是图书管理模块,里面包含两个类,图书馆类和图书类,用于进行书籍的管理。
图书类://201621123031public class Book { private String name; private long ID; private String category; public Book(String name,long ID,String category){………… } /**get()和set()和方法*/
图书馆类:
public class Library { public enum LendOrBack{ //用于变更借书还书后的书籍变化 LEND,BACK } private static Mapbooks=new TreeMap (); //用于存储图书信息及数量 static{ initializeBookStore(); } private static void initializeBookStore(){ Book book1 = new Book("红与黑",111,"文学类"); Book book2 = new Book("平凡的世界",112,"文学类"); Book book3 = new Book("双城记",113,"文学类"); Book book4 = new Book("资本论",114,"经济类"); Book book5 = new Book("孙子兵法",115,"军事类"); Book book6 = new Book("人间词话",116,"艺术类"); Book book7 = new Book("诗经",117,"艺术类"); Book book8 = new Book("楚辞",118,"艺术类"); Book book9 = new Book("战国策",119,"历史类"); Book book10 = new Book("国家地理",120,"地理类"); books.put(book1, 30); books.put(book2, 50); books.put(book3, 40); books.put(book4, 10); books.put(book5, 20); books.put(book6, 15); books.put(book7, 35); books.put(book8, 40); books.put(book9, 5); books.put(book10, 10); } public static Book peekBook(long ID) { //根据ID获取某本书籍的信息 Iterator > m = books.entrySet().iterator(); while(m.hasNext()){ Map.Entry e = m.next(); if(e.getKey().getID()==ID&&e.getValue()>0) return e.getKey(); } return null; } public static ArrayList peekBooks(String name) { //根据书名获取某本书籍的信息……………… } public static void add(Book book,int num){ //添加书籍 if(books.get(book)==null){ books.put(book, num); } else{ books.put(book, books.get(book)+num); } } public static void remove(Book book){ //移除书籍 books.remove(book); } public static void lendOrBack(Book book,LendOrBack action){ //进行借书还书操作时书籍数量的变更 switch(action) { case LEND:{ books.put(book, books.get(book)-1); break; } case BACK:{ books.put(book, books.get(book)+1); break; } } } public static void display(){ //展示图书馆的藏书 Iterator > m = books.entrySet().iterator(); System.out.println(" 书名 \t\t\tID\t\t类别\t\t数量"); while(m.hasNext()){ Map.Entry e = m.next(); if(e.getValue()>0) System.out.println(e.getKey().toString()+"\t\t"+e.getValue()); } System.out.println(); }}
7. 选做:使用其他方法解决题目5的生产者消费者问题。
7.1 使用BlockingQueue
解决生产者消费者问题关键代码截图
7.2 说明为什么不需要显示的使用wait、notify就可以解决同步问题。这样解决相比较wait、notify有什么优点吗?
答:BlockingQueue
是阻塞队列,当队列中没有数据的情况下,消费者线程会被自动阻塞,直到有数据进入队列,而当队列中填满数据的时候,生产者的所有线程都会被自动阻塞,直到队列中有空的位置,线程才会被自动唤醒。因此它不需要使用wait、notify就可以达到解决同步问题的方法,这样的方法比wait、notify更简单且更方便。
3.码云及PTA
题目集:多线程
3.1. 码云代码提交记录
- 在码云的项目中,依次选择“统计-Commits历史-设置时间段”, 然后搜索并截图
- 必须出现几个要素:提交日期-用户名(姓名与学号)-不提交说明
3.2 截图"多线程"PTA提交列表
需要有两张图(1. 排名图。2.PTA提交列表图)
3.3 统计本周完成的代码量
需要将每周的代码统计情况融合到一张表中。
周次 | 总代码量 | 新增代码量 | 总文件数 | 新增文件数 |
---|---|---|---|---|
2 | 607 | 607 | 15 | 15 |
3 | 1642 | 1035 | 33 | 18 |
5 | 2044 | 402 | 42 | 9 |
6 | 2874 | 830 | 57 | 15 |
7 | 3161 | 287 | 63 | 6 |
8 | 4299 | 1138 | 72 | 9 |
9 | 4831 | 532 | 81 | 9 |
10 | 5475 | 644 | 93 | 12 |
11 | 5958 | 483 | 102 | 9 |
12 | 6819 | 861 | 116 | 14 |
5. 选做:学会使用Eclipse进行调试
观看相关调试视频
5.1 简述使用Eclipse进行调试需要几步?调试时F5, F6, F7快键键各有什么不同?什么情况该使用哪个快捷键?
使用eclipse调试的步骤:
- 在需要调试的程序的左边栏上双击来设置断点
- 点击调试按钮进入调试透视图
- 以debug方式运行程序进行调试
F5:单步执行程序,遇到方法时进入。在碰到方法内部出现问题的时候,就要用F5去深入方法内部来进行调试。
F6:单步执行程序,遇到方法时跳过。在不用理会深层操作的时候使用F6。 F7:单步执行程序,从当前方法跳出。当进入某个方法内部想跳出来时使用F7。