1. 掌握Java语言中多线程编程的基本方法
2. 掌握Runnable接口实现多线程的方法
3. 掌握Thread类实现多线程的用法
4. 理解Socket的工作机理,掌握Socket编程的方法
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它已经有了相应的内存空间和其他资源。
运行:线程创建之后就具备了运行的条件,一旦轮到它来享用CPU资源时,即JVM将CPU使用权切换给该线程时,此线程的就可以脱离创建它的主线程独立开始自己的生命周期了(即run方法执行的过程)。
中断:有4种原因的中断,CPU资源从当前线程切换给其他线程、执行了sleep(int millsecond)方法、执行了wait()方法、进入阻塞状态 。
死亡:run方法结束。
在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异常对象。
同一时刻在等待队列中的线程会有很多个,它们各自任务的重要性有所不同。为了加以区分,使工作安排和资源分配时间更为合理,每个线程可以被赋予不同的优先级,让任务比较急的线程拥有更高的优先级,从而更快地进入执行状态。
Java中提供了10个等级的线程优先级,最低为Thread.MIN_PRIORITY=1,最高为Thread.MAX_PRIORITY=10,默认优先级为Thread.NORM_PRIORITY=5。
使用Thread类中的setPriority(int)方法可以为线程指定优先级。
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 方法“吵醒”自己。
线程同步是指几个线程都需要调用一个同步方法(使用关键字synchronized修饰的方法) 。
当一个线程A使用一个synchronized修饰的方法时,其他线程想使用这个方法时就必须等待,直到线程A 使用完该方法 (除非线程A使用wait主动让出CPU资源)。
一个线程在使用的同步方法中时,可能根据问题的需要,必须使用wait()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。其它线程如果在使用这个同步方法时如果不需要等待,那么它用完这个同步方法的同时,应当执行notifyAll()方法通知所有的由于使用这个同步方法而处于等待的线程结束等待。
挂起:
有时候两个线程并不是同步的,即不涉及都需要调用一个同步方法,但线程也可能需要暂时的挂起。所谓挂起一个线程就是让线程暂时让出CPU的使用权限,暂时停止执行,但停止执行的持续时间不确定,因此不能使用sleep方法暂停线程。挂起一个线程需使用wait方法,即让准备挂起的线程调用 wait 方法,主动让出CPU的使用权限。
恢复:
为了恢复该线程,其它线程在占有CUP资源期间,让挂起的线程的目标对象执行notifyAll()方法,使得挂起的线程继续执行;如果线程没有目标对象,为了恢复该线程,其它线程在占有CPU资源期间,让挂起的线程调用notifyAll()方法,使挂起的线程继续执行。
Java.net包中的URL类是对统一资源定位符的抽象,使用URL创建对象的应用程序称为客户端程序。一个URL对象存放着一个具体的资源的引用,表明客户要访问这个URL中的资源,利用URL对象可以获取URL中的资源。一个URL对象通常包含最基本的三部分信息:协议、地址、资源。协议必须是URL对象所在的Java虚拟机支持的协议;地址必须是能连接的有效IP地址或域名;资源可以是主机上的任何一个文件。URL对象调用InputStream openStream() 方法可以返回一个输入流,该输入流指向URL对象所包含的资源。通过该输入流可以将服务器上的资源信息读入到客户端。
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地址。
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(); } } } }
编写一个程序,动画显示文本域中的字符串。在窗体的南面添加三个按钮,为程序添加线程控制功能,要求点击开始按钮(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(); } } } }
下面是一个简单的字符界面的聊天程序。实验该程序,两人一组在局域网上实现简单的聊天功能。
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); } } }