实验六 多线程与网络程序设计

2021/11/13 17:11:02

本文主要是介绍实验六 多线程与网络程序设计,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

实验目的

1. 掌握Java语言中多线程编程的基本方法
2. 掌握Runnable接口实现多线程的方法
3. 掌握Thread类实现多线程的用法
4. 理解Socket的工作机理,掌握Socket编程的方法

实验导读

  1. 进程和线程的概念
    进程是程序一次动态执行的过程,对应从代码加载、执行到执行结束这样一个完整的过程,也是进程自身从产生、发展到消亡的过程。
    线程是比进程更小的执行单元,一个进程在执行过程中,可以产生多个线程。每个线程都有自身的产生、执行和消亡的过程。

2. 线程的状态与生命周期

 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空间和其他资源。
 运行:线程创建之后就具备了运行的条件,一旦轮到它来享用CPU资源时,即JVM将CPU使用权切换给该线程时,此线程的就可以脱离创建它的主线程独立开始自己的生命周期了(即run方法执行的过程)。
 中断:有4种原因的中断,CPU资源从当前线程切换给其他线程、执行了sleep(int millsecond)方法、执行了wait()方法、进入阻塞状态 。
 死亡:run方法结束。

3. 线程的创建

在Java语言中,与线程支持密切相关的是java.lang.Thread类和java.lang.Runnable接口。Runnable接口定义很简单,只有一个run方法。任何一个类如果希望自己的实例能够以线程的形式执行,都可以来实现Runnable接口。
继承Thread类和实现Runnable接口,都可以用来创建Thread对象,效果上并没有什么不同。继承Thread类的方法很明显的缺点就是这个类不能再继承其他的类了,而实现Runnable接口不会有这个麻烦。
另外,在继承Thread类的代码中,this其实就是指当前正在运行的线程对象,如果使用实现Runnable接口的方式,要得到当前正在执行的线程,需要使用Thread.currentThread()方法。
线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程,此线程必须调用start()方法(从父类继承的方法)通知JVM,这样JVM就会知道又有一个新一个线程排队等候切换了。
注意:多次启动一个线程,或者启动一个已经运行的线程对象是非法的,会抛出IllegalThreadStateException异常对象。

4. 线程的优先级

同一时刻在等待队列中的线程会有很多个,它们各自任务的重要性有所不同。为了加以区分,使工作安排和资源分配时间更为合理,每个线程可以被赋予不同的优先级,让任务比较急的线程拥有更高的优先级,从而更快地进入执行状态。
Java中提供了10个等级的线程优先级,最低为Thread.MIN_PRIORITY=1,最高为Thread.MAX_PRIORITY=10,默认优先级为Thread.NORM_PRIORITY=5。
使用Thread类中的setPriority(int)方法可以为线程指定优先级。

5. 线程的常用方法

 start() 方法:
线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
 run()方法:
Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。系统的Thread类中,run()方法没有具体内容,所以用户程序需要创建自己的Thread类的子类,并重写run()方法来覆盖原来的run()方法。当run方法执行完毕,线程就变成死亡状态。
 sleep(int millsecond) 方法:
线程占有CPU期间,执行sleep方法来使自己放弃CPU资源,休眠一段时间。休眠时间的长短由sleep方法的参数决定,millsecond是毫秒为单位的休眠时间。如果线程在休眠时被打断,JVM就抛出InterruptedException异常。因此,必须在try~catch语句块中调用sleep方法。
 isAlive() 方法:
线程处于“新建”状态时,线程调用isAlive()方法返回false。当一个线程调用start()方法,并占有CPU资源后,该线程的run方法就开始运行,在线程的run方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true。当线程进入“死亡”状态后(实体内存被释放),线程仍可以调用方法isAlive(),这时返回的值是false。
一个已经运行的线程在没有进入死亡状态时,不要再给线程分配实体,由于线程只能引用最后分配的实体,先前的实体就会成为“垃圾”,并且不会被垃圾收集机收集掉。
 currentThread() 方法:
currentThread()方法是Thread类中的类方法,可以用类名调用,该方法返回当前正在使用CPU资源的线程。
 interrupt() 方法:
intertupt方法经常用来“吵醒”休眠的线程。当一些线程调用sleep方法处于休眠状态时,一个占有CPU资源的线程可以让休眠的线程调用interrupt 方法“吵醒”自己。

6. 线程的同步

线程同步是指几个线程都需要调用一个同步方法(使用关键字synchronized修饰的方法) 。
当一个线程A使用一个synchronized修饰的方法时,其他线程想使用这个方法时就必须等待,直到线程A 使用完该方法 (除非线程A使用wait主动让出CPU资源)。
一个线程在使用的同步方法中时,可能根据问题的需要,必须使用wait()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。其它线程如果在使用这个同步方法时如果不需要等待,那么它用完这个同步方法的同时,应当执行notifyAll()方法通知所有的由于使用这个同步方法而处于等待的线程结束等待。
 挂起:
有时候两个线程并不是同步的,即不涉及都需要调用一个同步方法,但线程也可能需要暂时的挂起。所谓挂起一个线程就是让线程暂时让出CPU的使用权限,暂时停止执行,但停止执行的持续时间不确定,因此不能使用sleep方法暂停线程。挂起一个线程需使用wait方法,即让准备挂起的线程调用 wait 方法,主动让出CPU的使用权限。
 恢复:
为了恢复该线程,其它线程在占有CUP资源期间,让挂起的线程的目标对象执行notifyAll()方法,使得挂起的线程继续执行;如果线程没有目标对象,为了恢复该线程,其它线程在占有CPU资源期间,让挂起的线程调用notifyAll()方法,使挂起的线程继续执行。

7. 使用URL

Java.net包中的URL类是对统一资源定位符的抽象,使用URL创建对象的应用程序称为客户端程序。一个URL对象存放着一个具体的资源的引用,表明客户要访问这个URL中的资源,利用URL对象可以获取URL中的资源。一个URL对象通常包含最基本的三部分信息:协议、地址、资源。协议必须是URL对象所在的Java虚拟机支持的协议;地址必须是能连接的有效IP地址或域名;资源可以是主机上的任何一个文件。URL对象调用InputStream openStream() 方法可以返回一个输入流,该输入流指向URL对象所包含的资源。通过该输入流可以将服务器上的资源信息读入到客户端。

8. InetAddress类

Internet上的主机有两种方式表示地址:
 域名。例如,www.huat.edu.cn
 IP地址。例如,61.183.20.84
java.net包中的InetAddress类对象含有一个Internet主机地址的域名和IP地址:www.huat.edu.cn/61.183.20.84,域名比较容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
⑴ 获取本地机的地址
可以使用InetAddress类的静态方法getLocalHost()获得一个InetAddress对象,该对象含有本地机的域名和IP地址。
⑵ 获取Internet上主机的地址
可以使用InetAddress类的静态方法getByName(String s)获得一个InetAddress对象,将一个域名或IP地址传递给该方法的参数s,该对象含有主机地址的域名和IP地址。
另外,InetAddress类中含有两个实例方法:
public String getHostName() 获取InetAddress对象所含的域名
public String getHostAddress() 获取InetAddress对象所含的IP地址。

实验内容

  1. 线程接力(40分)
    编写一个应用程序,除了主线程外,还有三个线程:first、second和third。first负责模拟一个红色的按钮从坐标(10,60)运动到(100,60);second负责模拟一个绿色的按钮从坐标(100,60)运动到(200,60)。third线程负责模拟一个蓝色的按钮从坐标(200,60)运动到(300,60)。
    阅读并分析以下程序,将程序中的代码补充完整,编译并运行程序,查看结果。
import java.awt.*;
import java.awt.event.*;
public class MoveButton extends Frame implements Runnable, ActionListener {
	// 用Thread类声明first,second,third三个线程对象
	Thread first  ,second   ,third;          
	Button redButton, greenButton, blueButton, startButton;
	int distance = 10;
	MoveButton() {
		//分别创建first,second,third三个线程,用当前窗口做为该线程的目标对象
		first =   new Thread(this) ; 
		second =   new Thread(this)  ; 
		third =    new Thread(this)        ;
		redButton = new Button();
		greenButton = new Button();
		blueButton = new Button();
		redButton.setBackground(Color.red);
		greenButton.setBackground(Color.green);
		blueButton.setBackground(Color.blue);
		startButton = new Button("start");
		startButton.addActionListener(this);
		setLayout(null);
		add(redButton);
		redButton.setBounds(10, 60, 15, 15);
		add(greenButton);
		greenButton.setBounds(100, 60, 15, 15);
		add(blueButton);
		blueButton.setBounds(200, 60, 15, 15);
		add(startButton);
		startButton.setBounds(10, 100, 30, 30);
		setBounds(0, 0, 400, 200);
		setVisible(true);
		validate();
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
	}

	public void actionPerformed(ActionEvent e) {
		try {
			// 分别启动三个线程
			first.start();
			 second.start(); 
			 third.start(); 
		} catch (Exception exp) {
		}
	}

	public void run() {
		while (true) {
			// 判断当前占有CPU资源的线程是否是first
			if ( Thread .currentThread()==first  ) 
			{
				moveComponent(redButton);
				try {
					Thread.sleep(20);
				} catch (Exception exp) {
				}
			}
// 判断当前占有CPU资源的线程是否是second
			if ( Thread .currentThread()==second) 
			{
				moveComponent(greenButton);
				try {
					Thread.sleep(10);
				} catch (Exception exp) {
				}
			}
// 判断当前占有CPU资源的线程是否是third
			if ( Thread .currentThread()==third )
			{
				moveComponent(blueButton);
				try {
					Thread.sleep(20);
				} catch (Exception e) {
					
				}
			}
		}
	}
	public synchronized void moveComponent(Component b) {
		if (Thread.currentThread() == first) {
			while (distance > 100 && distance <= 300)
				try {
					wait();
				} catch (Exception exp) {
				}
			distance = distance + 1;
			b.setLocation(distance, 60);
			if (distance >= 100) {
				b.setLocation(10, 60);
				notifyAll();
			}
		}
		if (Thread.currentThread() == second) {
			while (distance > 100 && distance <= 300 )
				try {
					wait();
				} catch (Exception exp) {
				}
			distance = distance + 1;
			b.setLocation(distance, 60);
			if (distance > 200) {
				b.setLocation(100, 60);
				notifyAll();
			}
		}
		if (Thread.currentThread() == third) {
			while ( distance > 100 && distance <= 300  )
				try {
					wait();
				} catch (Exception exp) {
				}
			distance = distance + 1;
			b.setLocation(distance, 60);
			if (distance > 300) {
				distance = 10;
				b.setLocation(200, 60);
				notifyAll();
			}
		}
	}
}

2. 线程的控制(40分)

编写一个程序,动画显示文本域中的字符串。在窗体的南面添加三个按钮,为程序添加线程控制功能,要求点击开始按钮(startBtn),线程开始启动,文字逐个显示,并且将按钮状态改变为禁用(因为线程不能重复启动);点击暂停按钮(pauseBtn),线程暂停,文字显示停止;点击恢复按钮(resumeBtn),线程恢复运行,文字继续显示。当线程执行完毕后,恢复开始按钮的状态为可用。程序运行界面如下图所示:
在这里插入图片描述
阅读并分析以下程序,将程序中的代码补充完整,编译并运行程序,查看结果。

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.border.BevelBorder;
public class RunnableDemo extends JFrame implements Runnable, ActionListener {
	private JTextArea textArea; // 文本域组件
	JLabel label;
	Button startBtn, pauseBtn, resumeBtn;
	Panel panel;
	Thread thread;
	boolean move = false;

	// 动画显示的文本字符串
	private String introduction = "现在大家已经对计算机很熟悉了,如今计算机的操作"
			+ "系统可以同时执行多个任务,在听歌的同时能够打字、下载文件,在聊天窗口打"
			+ "字的时候,对方同时还能通过视频看到你;听到你。这一切都是使用多任务实现"
			+ "的,Java语言使用多线程实现一个程序中的多个任务同时运行。程序员可以在程"
			+ "序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机" 
			+ "制被称为多线程。";
	public static void main(String args[]) {
		new RunnableDemo(); // 创建本类实例对象
	}
	public RunnableDemo() {
		setTitle("线程的控制");
		label = new JLabel("多线程简介:"); // 标签组件
		getContentPane().add(label, BorderLayout.NORTH);// 添加标签到窗体		
		textArea = new JTextArea("\t"); // 初始化文本域组件
		textArea.setBorder(new BevelBorder(BevelBorder.LOWERED));// 设置边框
		textArea.setLineWrap(true); // 设置自动折行
		getContentPane().add(textArea, BorderLayout.CENTER);// 添加文本域组件到文本框
		startBtn =   new Button(" 开始"); 
		pauseBtn =  new Button(" 暂停");  
		resumeBtn =   new Button(" 恢复");  
		startBtn.addActionListener(this);
		pauseBtn.addActionListener(this);
		resumeBtn.addActionListener(this);
		panel = new Panel();
		panel.add(startBtn);
		panel.add(pauseBtn);
		panel.add(resumeBtn);
		getContentPane().add(panel, BorderLayout.SOUTH);
		setBounds(100, 100, 383, 225); // 设置窗体大小位置
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true); // 显示窗体
	}

	/**Runnable接口方法,是线程的执行方法*/
	@Override
	public void run() {
		textArea.setText("\t");
		String[] intros = introduction.split(""); // 将字符串分割为数组
		for (String ch : intros) { // ForEach遍历字符串数组
			while (!move) {
				try {
					synchronized (this) {
						wait();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			textArea.append(ch); // 添加一个字符到文本域
			try {
				   Thread.sleep(100)  // 线程休眠0.1秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		startBtn.setEnabled(true);
	}
	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == startBtn) {
			 move = true; 
			 Thread thread = new Thread(this);     
			  thread.start();        
			  startBtn.setEnabled(false);         
		} else if (e.getSource() == pauseBtn) {
			   move = false;    
		} else if (e.getSource() == resumeBtn) {
			 move = true;                                                    
			  synchronized (this) {                                                   
			    notify();                                                 
			  }                                                   
		}
	}
}

3. 练习使用Socket和ServerSocket进行套接字编程(10分)

下面是一个简单的字符界面的聊天程序。实验该程序,两人一组在局域网上实现简单的聊天功能。

服务器端程序如下:

import java.io.*;
import java.net.*;
public class ServerDemo {
	public static void main(String args[]) {
		try {
			//创建服务器端Socket,指定端口号4566
			ServerSocket server =  new  ServerSocket(4566);     ;
			Socket socket = server.accept();
			String line;
			BufferedReader is = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));
			PrintWriter os = new PrintWriter(socket.getOutputStream());
			BufferedReader sin = new BufferedReader(new InputStreamReader(
					System.in));
			System.out.println("Client:" + is.readLine());
			System.out.print("Server:");
			line = sin.readLine();
			while (!line.equals("bye")) {
				os.println(line);
				os.flush();
				System.out.println("Client:" + is.readLine());
				System.out.print("Server:");
				line = sin.readLine();
			}
			sin.close();
			os.close();
			is.close();
			socket.close();
			server.close();
		} catch (Exception e) {
			System.out.println("Error:" + e);
		}
	}
}

客户端程序如下:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ClientDemo {
	public static void main(String args[]) {
		try {
			//创建客户端Socket,指定服务器端IP地址和端口号
			Socket socket =  new Socket("127.0.0.1", 4566 );   ;
			BufferedReader is = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));
			PrintWriter os = new PrintWriter(socket.getOutputStream());
			BufferedReader sin = new BufferedReader(new InputStreamReader(
					System.in));
			String readline;
			System.out.print("Client:");
			readline = sin.readLine();
			while (!readline.equals("bye")) {
				os.println(readline);
				os.flush();
				System.out.println("Server:" + is.readLine());
				System.out.println("Client:");
				readline = sin.readLine();
			}
			sin.close();
			os.close();
			is.close();
			socket.close();
		} catch (Exception e) {
			System.out.println("Error:" + e);
		}
	}
}



这篇关于实验六 多线程与网络程序设计的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程