(1). 运行以下三个程序(要求每个程序运行10次),并对输出结果给出分析。在报告中附上程序截图和详细的文字说明。(15分)
程序1:
程序代码如上,此时都是打印一百次。多次运行代码后大体的结果类似下图,即a和b交替输出后才输出数字。
这是因为,打印字符比打印数字要快得多,因此线程1、2的存在时间很短。
将次数改为1000即可看到三者交替。
程序2:
代码如上,此时都是打印一百次。多次运行代码后大体的结果类似下图,即a和b交替输出后才输出数字。
程序3:
多次运行代码后发现,余额不是1就是2,并不是预期的100。
经过分析是没有处理好同步问题,多个线程拿到同一个变量,写回去后覆盖了。比如可能很多个都拿到余额为1,之后又都将其赋值为2。
解决的方法是要保证每个线程都单独处理余额balance。
在存款操作添加synchronized,保证其是原子操作即可。
如下图所示,再次运行后即为100。
(2). 编写Java应用程序实现如下功能:第一个线程输出数字1,2,..,12,第二个线程输出英文单词数字和月份One January, Two February, …, Twelve December,输出的顺序和格式为1OneJanuary2TwoFebruary...12TwelveDecember,即每1个数字紧跟着2个英文单词的方式。要求线程间实现通信。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程,而不是通过Thread类的子类的方式。在报告中附上程序截图、运行结果截图和详细的文字说明。(15分)
这段代码实现了两个线程分别输出数字和月份,并且要求数字和月份交替输出,即每输出一个数字后,接着输出两个英文单词的月份。以下是代码的主要思路:
共享资源与同步机制:
定义了一个共享的锁对象 lock,用于同步两个线程的操作。
使用 numberPrinted 变量标识数字是否已经打印,以确保数字和月份的交替输出。
数字线程 (NumberPrinter):
实现了 Runnable 接口,用于创建一个线程对象。
在 run 方法中,通过 synchronized (lock) 同步块,确保多个线程访问临界区时的互斥性。
使用 while 循环检查 numberPrinted 的状态,如果数字已经打印,则进入等待状态(lock.wait()),直到被唤醒。
输出数字并设置 numberPrinted 为 true,然后唤醒等待的线程(月份线程)。
月份线程 (MonthPrinter):
同样实现了 Runnable 接口,创建了另一个线程对象。
在 run 方法中,同样使用 synchronized (lock) 同步块,确保对共享资源的访问是同步的。
使用 while 循环检查 numberPrinted 的状态,如果数字未打印,则进入等待状态(lock.wait()),直到被唤醒。
输出月份并设置 numberPrinted 为 false,然后唤醒等待的线程(数字线程)。
主方法 (main):
创建两个线程对象,分别用于输出数字和月份。
启动这两个线程。
同步控制:
通过 synchronized 关键字和 wait()、notify() 方法实现线程之间的同步和通信。
在共享资源被修改时,通过 wait() 让当前线程进入等待状态,而在修改完成后通过 notify() 唤醒等待的线程。
(3). 编写Java应用程序实现如下功能:创建工作线程,模拟银行现金账户取款操作。多个线程同时执行取款操作时,如果不使用同步处理,会造成账户余额混乱,要求使用syncrhonized关键字同步代码块,以保证多个线程同时执行取款操作时,银行现金账户取款的有效和一致。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程,而不是通过Thread类的子类的方式。在报告中附上程序截图、运行结果截图和详细的文字说明。(25分)
BankAccount 类:
BankAccount 类表示银行账户,包含了一个私有成员变量 balance 表示账户余额。
构造方法 public BankAccount(int balance) 用于初始化账户余额。
方法 public synchronized void withdraw(int amount) 为同步方法,用于进行取款操作。在该方法中,通过 synchronized 关键字确保了多个线程对账户的安全访问。
WithdrawThread 类:
WithdrawThread 类实现了 Runnable 接口,表示取款线程。
构造方法 public WithdrawThread(BankAccount account) 接受一个 BankAccount 对象,表示共享的银行账户。
public void run() 方法是实现 Runnable 接口的方法,其中包含了取款的逻辑。通过调用 account.withdraw(amount) 方法进行同步的取款操作。
Third 类(主类):
Third 类包含了 main 方法,是程序的入口。
在 main 方法中,创建了一个初始余额为 500 的银行账户对象 account。
创建两个取款线程 thread1 和 thread2,它们共享同一个银行账户对象。
启动两个线程,它们会并发执行取款操作。
运行过程解释:
每个取款线程会执行 5 次取款操作,每次取款的金额是随机生成的。
在 withdraw 方法中,通过同步机制确保了多个线程对账户的安全访问,避免了余额混乱的问题。
程序的输出中,通过 System.out.println 输出了每次取款的结果,包括取款成功或失败以及余额信息。
(4). 有一座东西向的桥,只能容纳一个人,桥的东边有20个人(记为E1,E2,…,E20)和桥的西边有20个人(记为W1,W2,…,W20),编写Java应用程序让这些人到达对岸,每个人用一个线程表示,桥为共享资源,在过桥的过程中输出谁正在过桥(不同人之间用逗号隔开)。运行10次,分别统计东边和西边的20人先到达对岸的次数。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程,而不是通过Thread类的子类的方式。在报告中附上程序截图、运行结果截图和详细的文字说明。(25分)
代码模拟了一座桥,有东边和西边两侧的人,每个人都是一个线程。这些人需要过桥,但桥只能容纳一个人,因此需要同步控制多个线程的过桥操作。
代码的主要思路:
创建一个 Person 类,实现 Runnable 接口,表示每个过桥的人。
在 Person 类中,通过 synchronized 关键字锁定一个对象(在这里是 lock 对象),以确保同一时刻只有一个人能够通过桥。
在 run() 方法中,通过 synchronized 锁定的代码块,模拟每个人过桥的操作。在过桥时,输出人的名字,并记录最后一个过桥的人是从东边过来的还是从西边过来的。
在主程序中,创建多个东边和西边的人线程,并启动它们。
在 runSimulation() 方法中,通过 joinThreads() 方法等待所有线程执行完成。
输出每次模拟的统计结果,统计东边和西边先过桥的次数。
(by 归忆)