Java必实验四:线程应用

一、 实验目的

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,print100
		Runnable 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、占用一定的内存空间。

       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类对象r
        Thread t1,t2;
        t1 = new Thread(new Task1(r));
        t2 = new Thread(new Task2(r));//创建两个线程t1,t2
        t1.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;//设置账户初始余额为1000
    public 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,t3
        t1.start();
        t2.start();
        t3.start();//启动线程
    }
}

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值