201871010126 王亚涛 《面向对象程序设计 (Java)》第十七周学习总结
|
内容 |
这个作业属于哪个课程 |
https://www.cnblogs.com/nwnu-daizh/ |
这个作业的要求在哪里 |
https://www.cnblogs.com/nwnu-daizh/p/12073034.html |
作业学习目标 |
(1) 理解和掌握线程的优先级属性及调度方法; (2) 掌握线程同步的概念及实现技术; (3) Java线程综合编程练习 |
随笔博文正文内容包括:
第一部分:总结线程同步技术(10分)
14.5 同步
14.5.1 多线程同步
多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理。显然这是由于全局资源造成的,有时为了解决此问题,优先考虑使用局部变量,退而求其次使用同步代码块,出于这样的安全考虑就必须牺牲同步:A线程要请求某个资源,但是此资源正在被B线程使用中,因为同步机制存在,A线程请求不到,怎么办,A线程只能等待下去
14.5.2竟争条件
14.5.2.1 什么是竞争条件
Java中的竞争条件是一种并发错误或问题,它是在您的程序中引入的,因为您的程序在多个线程同时并行执行,因为Java是一种多线程编程语言,因此在Java中竞争条件的风险更高要求清楚了解导致竞赛状况的原因以及如何避免这种情况。反正竞争条件只是危险或风险在Java中使用多线程的呈现就像在Java中的僵局。当两个线程在没有正确同步的同一对象上操作并且操作彼此交错时,就会出现竞争条件。C lassical 竞争条件的例子是增加一个计数器,因为增量不是一个原子操作,可以进一步分为三个步骤,如读,更新和写。如果两个线程同时尝试增加计数,并且由于一个线程的读取操作与另一个线程的更新操作交错而读取相同的值,则一个线程覆盖其他线程完成的增量时,将丢失一个计数。原子操作不受竞态条件的限制,因为这些操作不能交错。这也是Java核心访谈期间流行的多线程面试问题。
14.5.2.2 查找竞争条件
用任何语言查找竞争条件都是最困难的工作,而Java也没有什么不同,不过由于Java代码的可读性非常好,并且同步构造是通过代码审查找到竞争条件的良好定义堆。由于竞争条件的随机性,通过单元测试发现竞争条件并不可靠。因为竞争条件只有一段时间才会通过单元测试而不会面临任何竞争条件。只有确定的方式来找到竞争条件是手动审查代码或使用代码审查工具,它可以根据代码模式和Java中的同步使用提示潜在的竞争条件。我仅仅依赖于代码审查和尚未找到合适的Java中暴露竞态条件的工具 。
14.5.3 锁对象 条件对象
锁对象
临界区:临界区是一个特殊的代码段,该代码段访问某种特殊的公共资源,该资源同一时间只允许一个线程使用。
Java中可以使用锁对象创造一个临界区:
1 myLock.lock(); 2 try { 3 关键代码 4 } finally { 5 myLock.unlock(); 6 }
有关锁对象和条件对象的关键要点:
- 锁用来保护代码片段,保证任何时刻只能有一个线程执行被保护的代码。
- 锁管理试图进入被保护代码段的线程。
- 锁可拥有一个或多个相关条件对象。每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。
synchronized关键字
synchronized关键字作用:
➢某个类内方法用synchronized 修饰后,该方法被称为同步方法;
➢只要某个线程正在访问同步方法,其他线程欲要访问同步方法就被阻塞,直至线程从同步方法返回前唤醒被阻塞线程,其他线程方可能进入同步方法。
3、在同步方法中使用wait()、notify 和notifyAll()方法
➢ 一个线程在使用的同步方法中时,可能根据问题的需要,必须使用wait()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。
➢ 线程如果用完同步方法,应当执行notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待。
第二部分:实验部分
实验1:测试程序并进行代码注释。
测试程序1:
l 在Elipse环境下调试教材651页程序14-7,结合程序运行结果理解程序;
l 掌握利用锁对象和条件对象实现的多线程同步技术。
实验程序代码如下:
package synch; /** * 这个程序展示了多线程如何安全地访问数据结构。 * @version 1.31 2015-06-21 * @author Cay Horstmann */ public class SynchBankTest { public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; public static final double MAX_AMOUNT = 1000; public static final int DELAY = 10; public static void main(String[] args) { Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { int fromAccount = i; Runnable r = () -> { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = MAX_AMOUNT * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch (InterruptedException e) { } }; Thread t = new Thread(r); t.start(); } } }
package synch; import java.util.*; import java.util.concurrent.locks.*; /** * 一种拥有许多银行帐户的银行,它使用锁来序列化访问。 * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { private final double[] accounts; private Lock bankLock;//锁对象 private Condition sufficientFunds; /** *构建了银行。 * @param 账户数量 * @param 每个账户的初始余额 */ public Bank(int n, double initialBalance) { accounts = new double[n]; Arrays.fill(accounts, initialBalance); bankLock = new ReentrantLock(); sufficientFunds = bankLock.newCondition(); } /** * 把钱从一个账户转到另一个账户。 * @param 从账户转出 * @param 到账转到 * @param 转帐金额 */ public void transfer(int from, int to, double amount) throws InterruptedException { bankLock.lock();//加锁 try { while (accounts[from] < amount) sufficientFunds.await();//注释掉之后产生死锁现象,都在等待 System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); sufficientFunds.signalAll();//注释掉之后产生死锁现象,都在等待 } finally { bankLock.unlock();//解锁 } } /** *获取所有帐户余额的总和。 * @return 总平衡 */ public double getTotalBalance()//为什么只需要加锁不需要设置对象?没有任何执行不下去的原因,就不需要条件对象。 { bankLock.lock(); try { double sum = 0; for (double a : accounts) sum += a; return sum; } finally { bankLock.unlock(); } } /** * 获取银行中的帐户编号。 * @return 账户数量 */ public int size() { return accounts.length; } }
运行截图如下:
测试程序2:
l 在Elipse环境下调试教材655页程序14-8,结合程序运行结果理解程序;
l 掌握synchronized在多线程同步中的应用。
实验程序代码及注释如下:
package synch2; /** * 这个程序展示了多线程如何安全地访问一个数据结构,使用同步方法。 * @version 1.31 2015-06-21 * @author Cay Horstmann */ public class SynchBankTest2 { public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; public static final double MAX_AMOUNT = 1000; public static final int DELAY = 10; public static void main(String[] args) { Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { int fromAccount = i; Runnable r = () -> { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = MAX_AMOUNT * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch (InterruptedException e) { } }; Thread t = new Thread(r); t.start(); } } }
package synch2; import java.util.*; /** * 使用同步原语的具有多个银行帐户的银行 * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { private final double[] accounts; /** * 构建了银行。 * @param 账户数量 * @param 每个账户的初始余额 */ public Bank(int n, double initialBalance) { accounts = new double[n]; Arrays.fill(accounts, initialBalance); } /** * 把钱从一个账户转到另一个账户。 * @param 从账户转出 * @param 到账转到 * @param 转帐金额 */ public synchronized void transfer(int from, int to, double amount) throws InterruptedException { while (accounts[from] < amount) wait();//导致线程进入等待状态直到它被通知。该方法只能在一个同步方法中调用。
System.out.print(Thread.currentThread());//打印出线程号
accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to);/第一个打印结果保留两位小数
accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());//解除那些在该对象上调用wait方法的线程阻塞状态。该方法只能在同步方法或同步块内部调用
notifyAll(); } /** *获取所有帐户余额的总和。 * @return 总平衡 */ public synchronized double getTotalBalance() { double sum = 0; for (double a : accounts) sum += a; return sum; } /** * 获取银行中的帐户编号。 * @return 账户数量 */ public int size() { return accounts.length; } }
运行截图如下:
测试程序3:
l 在Elipse环境下运行以下程序,结合程序运行结果分析程序存在问题;
l 尝试解决程序中存在问题。
class Cbank { private static int s=2000; public static void sub(int m) { int temp=s; temp=temp-m; try { Thread.sleep((int)(1000*Math.random())); } catch (InterruptedException e) { } s=temp; System.out.println("s="+s); } }
class Customer extends Thread { public void run() { for( int i=1; i<=4; i++) Cbank.sub(100); } } public class Thread3 { public static void main(String args[]) { Customer customer1 = new Customer(); Customer customer2 = new Customer(); customer1.start(); customer2.start(); } } |
实验程序代码如下:
package wyt; class Cbank { private static int s=2000; public static void sub(int m) { int temp=s; temp=temp-m; try { Thread.sleep((int)(1000*Math.random())); } catch (InterruptedException e) { } s=temp; System.out.println("s="+s); } } class Customer extends Thread { public void run() { for( int i=1; i<=4; i++) Cbank.sub(100); } } public class Thread3 { public static void main(String args[]) { Customer customer1 = new Customer(); Customer customer2 = new Customer(); customer1.start(); customer2.start(); } }
运行截图如下:
修改后的程序代码如下:
package wyt; class Cbank { private static int s=2000; public synchronized static void sub(int m) { int temp=s; temp=temp-m; try { Thread.sleep((int)(1000*Math.random())); } catch (InterruptedException e) { } s=temp; System.out.println("s="+s); } } class Customer extends Thread { public void run() { for( int i=1; i<=4; i++) Cbank.sub(100); } } public class Thread3 { public static void main(String args[]) { Customer customer1 = new Customer(); Customer customer2 = new Customer(); customer1.start(); customer2.start(); } }
实验截图如下:
实验2 编程练习
利用多线程及同步方法,编写一个程序模拟火车票售票系统,共3个窗口,卖10张票,程序输出结果类似(程序输出不唯一,可以是其他类似结果)。
Thread-0窗口售:第1张票
Thread-0窗口售:第2张票
Thread-1窗口售:第3张票
Thread-2窗口售:第4张票
Thread-2窗口售:第5张票
Thread-1窗口售:第6张票
Thread-0窗口售:第7张票
Thread-2窗口售:第8张票
Thread-1窗口售:第9张票
Thread-0窗口售:第10张票
结对编程实验代码如下:
package wyt; import java.nio.charset.MalformedInputException; public class w { public static void main(String[] args) { Mythread mythread= new Mythread(); Thread t1 = new Thread(mythread); Thread t2 = new Thread(mythread); Thread t3 = new Thread(mythread); t1.start(); t2.start(); t3.start(); } } class Mythread implements Runnable{ int t=1; boolean flag=true; public void run() { while(flag) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (this) { if(t<=10){ System.out.println(Thread.currentThread().getName()+"窗口售:第"+t+"张票"); t++; } if(t>10){ flag=false; } } } } }
运行截图如下:
结对编程图片如下:
实验总结:(5分)
1):通过本次课程的学习让我们对Java中第十四章相关内容有了进一步了解,第十四章主要是线程的相关知识,而这次的侧重于线程同步技术的相关特点的学习,在上课老师的讲解过程中我们对线程有了进一步了解,理解和掌握了线程的优先级属性及调度方法;掌握线程同步的概念及实现技术。
2):通过老师上课详细的讲解和实验过程中对不懂得知识的查找资料及同学的讲解我们对第十四章的知识点有了进一步的强化认识,在做作业的过程中遇到了一些问题,但都通过问同学或查资料一一解决了,在讨论的过程中了解了自己的不足,掌握了许多不知道不熟悉的知识,由于对知识点了解的不透彻,在结对编程的过程中遇到了许多问题,意识到在课下应该多看书及查看资料来巩固上课所学的知识有进一步熟悉及掌握。
3).通过这次的实验使我学到了不少实用的知识,并且锻炼了自己的动手能力;虽然在学习的过程中遇到了许多不懂得地方,但是课下都通过问同学及查找相关资料等一些方式都一一解决了;在这次实验中意识到多学习和动手是很重要的,应在课下多敲代码、多练习,通过不断努力,对Java相关知识进一步强化认识。