一、 实验目的 1. 掌握Java程序设计中的线程同步等技术 。
二、实验内容与要求 (1). 运行以下三个程序(每个程序运行10次) , 并对输出结果给出分析 。
(2). 编写Java应用程序实现如下功能:第一个线程生成一个随机数 , 第二个线程每隔一段时间读取第一个线程生成的随机数 , 并判断它是否是奇数 。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程 , 而不是通过Thread类的子类的方式 。在报告中附上程序截图、完整的运行结果截图和简要文字说明 。(20分)
(3). 编写Java应用程序实现如下功能:第一个线程输出数字1-26 , 第二个线程输出字母A-Z , 输出的顺序为1A2B3C...26Z , 即每1个数字紧跟着1个字母的方式 。要求线程间实现通信 。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程 , 而不是通过Thread类的子类的方式 。在报告中附上程序截图、运行结果截图和详细的文字说明 。(20分)
(4). 编写Java应用程序实现如下功能:创建工作线程 , 模拟银行现金账户存款操作 。多个线程同时执行存款操作时 , 如果不使用同步处理 , 会造成账户余额混乱 , 要求使用syncrhonized关键字同步代码块 , 以保证多个线程同时执行存款操作时 , 银行现金账户存款的有效和一致 。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程 , 而不是通过Thread类的子类的方式 。在报告中附上程序截图、运行结果截图和详细的文字说明 。(25分)
三、实验过程及结果 1.运行以下三个程序(要求每个程序运行10次) , 并对输出结果给出分析 。
程序1:
package example1;//PrintChar任务类 , 实现打印指定次数的某个字符的任务class PrintChar implements Runnable{//由Runnable接口实现PrintChar类 private char charToPrint;//待打印的字符charToPrint private int times; //打印次数 public PrintChar(char c, int t) {//带参构造方法 , 用于设置成员变量charToPrint = c;times = t; } //重写Runnable接口中的run()方法 , 定义新的规则 , 即多次打印字符 @Override public void run() {for(int i = 0; i < times; i++) {System.out.print(charToPrint);} }}//PrintNum任务类 , 实现逐个打印从1到lastNum的任务class PrintNum implements Runnable{//继承Runnable接口 private int lastNum; public PrintNum(int n) {lastNum = n; } //重写Runnable接口中的run()方法 , 定义新的规则 , 即打印1到lastNum @Override public void run() {for(int i = 1; i <= lastNum; i++) {System.out.print(" " + i);} }}public class TaskThreadDemo { static public void main(String[] args) {//创建任务 , 创建可运行对象printA , printB , print100Runnable printA = new PrintChar('a', 100);Runnable printB = new PrintChar('b', 100);Runnable print100 = new PrintNum(100);//利用以上可运行对象及Thread类创建线程对象thread1 , thread2 , thread3 , 将Runnale接口的子类对象作为实际的参数传递给Thread 类的构造函数Thread thread1 = new Thread(printA);Thread thread2 = new Thread(printB);Thread thread3 = new Thread(print100);//用以上三个线程对象启动线程thread1.start();thread2.start();thread3.start();}} 程序运行10次后 , 每次运行的结果都不一样 , thread1 , thread2 , thread3三个线程在执行各自的任务时不是按程序顺序有规律地运行 , 而是无规律地交替执行 。原因是当有多个线程的时候 , 线程之间属于竞争状态 , 交替占用CPU资源 , 因此运行结果是带有随机性的 。
使用多线程的优缺点:
优点: 1、适当的提高程序的执行效率(多个线程同时执行) 。
2、适当的提高了资源利用率(CPU、内存等) 。
缺点: 1、占用一定的内存空间 。
【Java必实验四:线程应用】2、线程越多CPU的调度开销越大 。
3、程序的复杂度会上升 。
程序2:
package example1;import java.util.concurrent.*;class PrintChar1 implements Runnable{ private char charToPrint; private int times; public PrintChar1(char c, int t) {charToPrint = c;times = t; }@Override public void run() {for(int i = 0; i < times; i++) {System.out.print(charToPrint);} }}class PrintNum1 implements Runnable{ private int lastNum; public PrintNum1(int n) {lastNum = n; }@Override public void run() {for(int i = 1; i <= lastNum; i++) {System.out.print(" " + i);} }}public class ExecutorDemo { static public void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(3);//使用Executor类获取一个ThreadPoolExecutor线程池 , 创建容器大小为n的线程池 , 表示正在执行中的线程只有n个 , 若有超过n个线程 , 则需排队//将线程放进池子里执行任务executor.execute(new PrintChar1('a', 100));executor.execute(new PrintChar1('b', 100));executor.execute(new PrintNum1(100));executor.shutdown();//池子中没有任务时关闭线程 , 所有任务都执行完 }} 运行程序10次后 , 发现每次运行的结果也不尽相同 , 并不是先把a、b两个字母一次性多次打印完后再输出数字 , 而是交替随机输出 , 但交替输出的同时 , 总是有优先输出a、b , 再按顺序输出数字的趋势 。
线程池有如下的优势:
(1)降低资源消耗 。通过重复利用已创建的线程降低线程创建和销毁造成的消耗 。
(2)提高响应速度 。当任务到达时 , 任务可以不需要等到线程创建就能立即执行 。
(3)提高线程的可管理性 。线程是稀缺资源 , 如果无限制的创建 , 不仅会消耗系统资源 , 还会降低系统的稳定性 , 使用线程池可以进行统一的分配 , 调优和监控 。
程序3:
package example1;import java.util.concurrent.*;public class AccountWithoutSync { private static Account account = new Account();//new一个私有的Account类静态对象account static public void main(String[] args) {//创建一个可缓存的线程池 , 调用execute 将重用以前构造的线程(如果线程可用) 。如果没有可用的线程 , 则创建一个新线程并添加到池中 。终止并从缓存中移除那些已有 60 秒钟未被使用的线程 。ExecutorService executor = Executors.newCachedThreadPool();for(int i = 0; i < 100; i++) { executor.execute(new AddAPennyTask());//execute执行Runnable类型的AddAPennyTask任务 , 所属顶层接口为Executor}executor.shutdown();//池子中没有任务时关闭线程 , 所有任务都执行完while(!executor.isTerminated()) {//isTerminated() , 当调用shutdown()方法后 , 并且所有提交的任务完成后返回为true}System.out.println("What is balance? " + account.getBalance()); } //AddAPennyTask 任务类继承Runnable接口 private static class AddAPennyTask implements Runnable{public void run() {account.deposit(1);} } //线程同步 , 同步方法 private static class Account{private int balance = 0;public int getBalance() {return balance;}public void deposit(int amount) {int newBalance = balance + amount;//try-catch代码块抛出异常try {Thread.sleep(5);//线程休眠5ms}catch(InterruptedException ex) {}balance = newBalance;} }} 程序运行10次后 , 第一次运行结果为“What is balance? 1” , 随后多次的运行的结果均为“What is balance? 2” , 而后我又增加了创建和启动线程的数目 , balance值也相应增加 。
2. 编写Java应用程序实现如下功能:第一个线程生成一个随机数 , 第二个线程每隔一段时间读取第一个线程生成的随机数 , 并判断它是否是奇数 。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程 , 而不是通过Thread类的子类的方式 。
package example2;class Random{//定义Random类产生随机数private int num;//synchronized修饰getRandomNumber()方法 , 线程Thread1进到同步代码块后 , 会将同步代码块中的代码全部锁住 , 当线程Thread2也进到此处 , 线程Thread2只能处于等待状态 , 需要等到线程Thread1执行完同步代码后才能够进入 。public synchronized void getRandomNum(){for(int i = 0; i < 10; i++){//循环10次 , 读取10个随机数num = (int)(Math.random() * 1000);//sleep()和wait()方法需要抛出中断异常 , 判断当前进程是否处于interrupted状态try{Thread.sleep(105);//线程挂起(休眠)105ms}catch(InterruptedException e){}notify();//唤醒在此对象监视器等待的单个线程try{wait(); //当某个线程获取到锁后 , 发现当前还不满足执行的条件 , 就可以调用对象锁的wait方法 , 进入等待状态 。}catch(InterruptedException e){}}}//synchronized关键字实现每隔一段时间读取随机数public synchronized void isOddNumber(){for(int i = 0; i < 10; i++){System.out.println("每隔500ms后产生的随机数为" + num);if(num % 2 != 0){//判断是否为奇数 , 不能被2整除的为奇数System.out.println("该数字为奇数");}else{System.out.println("该数字为偶数");}以下try-catch代码块为受Thread.sleep() 和 Object.wait() 支持的中断机制 , 它允许一个线程请求另一个线程停止它正在做的事情 。当抛出InterruptedException 异常时 , 会停止当前进程而提前返回 。try{Thread.sleep(500);//休眠500ms , 即每隔500ms输出信息}catch(InterruptedException e){}notify();try{wait();}catch(InterruptedException e){}}}}class Task1 implements Runnable{//Task1继承Runnable接口private Random random;//声明Random类对象random作为Task1的数据成员public Task1(Random r){ //构造方法random = r;}//重写Runnable接口的run方法 , 方法内调用Random类的getRandomNum()方法 , 完成获取随机数的任务public void run(){random.getRandomNum();}}class Task2 implements Runnable{private Random random;public Task2(Random r){random = r;}//重写Runnable接口的run方法 , 方法内调用Random类的isOddNumber()方法 , 完成判断是否是奇数的任务public void run(){random.isOddNumber();}}public class Example2_1 {public static void main(String args[]){Random r = new Random();//声明Random类对象rThread t1,t2;t1 = new Thread(new Task1(r));t2 = new Thread(new Task2(r));//创建两个线程t1 , t2t1.start();t2.start();//调用start()方法执行线程 , 同时run()方法会被调用}}
通过查资料 , 我还了解到java有这样一种中断策略:
1.提供一种标记方式 , 用来标记是否需要中断 。
2.提供一种检测标记状态方式 , 检测该标记 。
3.对于简单的阻塞状态(可响应中断) , 通过抛出InterruptedException异常的方式 。
4.对于复杂的阻塞状态(不可响应中断) , 通过上层主动在代码中判断该标记的状态 , 去决定各种自定义的处理方式 。
3. 编写Java应用程序实现如下功能:第一个线程输出数字1-26 , 第二个线程输出字母A-Z , 输出的顺序为1A2B3C...26Z , 即每1个数字紧跟着1个字母的方式 。要求线程间实现通信 。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程 , 而不是通过Thread类的子类的方式 。
package example3;class Print{//使用关键字synchronized实现线程的同步 , 在执行一个线程时 , 其他线程会排队 等候 , 该线程结束 。public synchronized void printNumber(){for(int i = 1; i <= 26; i++){System.out.print(i);//输出数字1至26//try-catch代码块抛出异常try{Thread.sleep(50);//线程休眠50ms}catch(InterruptedException e){}notify();try{wait();}catch(InterruptedException e){}}}public synchronized void printChar(){for(int i = 0; i < 26; i++){System.out.print((char)('A'+ i));//输出A至Z 26个字母//try-catch代码块抛出异常try{Thread.sleep(50);//线程休眠50ms}catch(InterruptedException e){}notify(); //唤醒在此对象监视器等待的单个线程try{wait();//当某个线程获取到锁后 , 发现当前还不满足执行的条件 , 就可以调用对象锁的wait方法 , 进入等待状态 。}catch(InterruptedException e){}}}};class Number implements Runnable{private Print p;public Number(Print pp){p = pp;}public void run(){p.printNumber();}};class Char implements Runnable{private Print p;public Char(Print pp){p = pp;}public void run(){p.printChar();}};public class Example3_1 {public static void main(String args[]){Print p = new Print();Thread t1,t2;t1 = new Thread(new Number(p));t2 = new Thread(new Char(p));t1.start();t2.start();}}
4. 编写Java应用程序实现如下功能:创建工作线程 , 模拟银行现金账户存款操作 。多个线程同时执行存款操作时 , 如果不使用同步处理 , 会造成账户余额混乱 , 要求使用syncrhonized关键字同步代码块 , 以保证多个线程同时执行存款操作时 , 银行现金账户存款的有效和一致 。要求采用实现Runnable接口和Thread类的构造方法的方式创建线程 , 而不是通过Thread类的子类的方式 。
package example4;import java.util.concurrent.*;//Account类实现存款并计算余额 , 输出信息的功能class Account{private int balance = 1000;//设置账户初始余额为1000public synchronized void deposit(String threadname, int b){balance += b;//计算存款后余额System.out.println(threadname + " 存款金额为: " + b + " 账户余额为: " + balance);//输出当前线程名称、存款金额及账户余额notifyAll();//notifyAll()使所有原来在该对象上等待被notify的所有线程统统退出wait的状态 , 变成等待该对象上的锁 , 一旦该对象被解锁 , 他们就会去竞争 。//try-catch代码块抛出异常try{Thread.sleep(100);//休眠(挂起)100ms}catch(InterruptedException e){}try{wait();//wait()暂时停止目前进程的执行 , 直到有信号来到或子进程结束 。}catch(InterruptedException e){}}};//Money类继承Runnable接口 , 完成存款任务class Money implements Runnable{private Account acc;//声明Account类对象acc , 作为实例变量private int money;public Money(Account acc, int money){this.acc = acc;this.money = money;}public void run(){Thread t = Thread.currentThread();//Thread.currentThread()可以获取当前线程的引用 , 此处用于在没有线程对象又需要获得线程信息时通过Thread.currentThread()获取当前代码段所在线程的引用 。acc.deposit(t.getName(), money);//Thread.currentThread().getName()获得当前线程名称}}public class Example4_1 {public static void main(String args[]){Account task = new Account();//声明task对象Thread t1, t2, t3;t1 = new Thread(new Money(task, 7));t2 = new Thread(new Money(task, 9));t3 = new Thread(new Money(task, 11));//用Thread类的构造方法创建线程t1 , t2 , t3t1.start();t2.start();t3.start();//启动线程}}
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
