丁保国的博客

收集整理工作生活,点点滴滴

  :: :: :: :: :: 管理 ::

第二十一讲 Java中的多线程编程

第二十一讲 Java中的多线程编程

主要内容

初识Java多线程

线程的生命周期

Java多线程程序设计方法

多线程应用示例

线程的优先级、同步*

 

初识多线程

一个简单的多线程的示例

class ManyThread extends Thread{

  int n=0;

 

  public static void main(String[] aa){

     ManyThread t1=new ManyThread();

     ManyThread t2=new ManyThread();

     System.out.println("Thread1 start!");

     t1.start();

     System.out.println("Thread2 start!");

     t2.start();

     System.out.println("Ok");

  }

  public void run(){

     while(n<10){

        n++;

      System.out.println(currentThread().getName()+"\t"+n);

     }

  }

}

结论:

²       线程类名为Thread,可由Thread及其子类创建线程对象。

²       可通过Thread.currentThread()可测得当前线程

²       线程是由start启动的,启动后会自动执行run方法。

 

单线程和多线程

线程:一个程序中彼此分离的、能独立运行的子任务。

单线程和排队等候

没有特意创建线程的程序为单线程程序, Java能自动创建和控制线程。

单线程的本质是排队等候,一个任务必须在另一个任务之后执行。

资源利用率低,CPU在大多时候空闲

 

多线程和并行执行

多线程的程序将任务分成几个子任务,多个子任务并行操作。(抢占式)

 

 

 

思考

1.    多线程程序的运行方式是?

2.    一个任务运行完毕,再执行另一个任务,这是单线程还是多线程?

3.    多线程的程序有什么优越之处?

4.    哪种程序有更好的用户响应能力?

5.    线程是如何启动的?

6.    线程启动后具体执行的方法是?

7.    在多线程工作时,怎么测得当前占据CPU运行时刻的线程?

 

线程的生命周期

在一个线程的生命周期中,它总处于某一种状态中。

Thread类及其主要方法

 

构造方法

public Thread():用缺省名称创建一个Thread对象

public Thread(String name):用指定名称创建一个Thread对象

 

线程的常用方法

说明

任务

public void start()

启动一个线程

public void run()

执行线程

 

休眠

public void stop()

停止线程

public static Thread currentThread()

必须throws InterruptedException

返回当前线程

public String getName()

返回线程的名称

 

线程的状态

线程的状态通常可归纳为:新生态、运行态、不可运行态、死亡态四种状态。

 

新生态

时机:当创建了类Thread或其子类的实例对象时,一个新的线程就产生了。

ManyThread t1=new ManyThread()

线程去向:只能启动或终止该线程。调用start()和stop()之外的其它方法都会失败并且会引起非法状态处理。

 

运行态( Runnable )

时机:对新生态的线程执行start()方法进入运行态,此时该线程处于可运行状态。

t1.start();

线程去向:进入不可运行的状态(等待或睡眠)和死亡态。

说明:

²       处于运行态的线程都在运行或侍机运行。

²       单处理器的计算机,每一时刻真正处于运行中的线程只有一个。

²       JavaVM实现调度来保证这些线程共享处理器。

 

不可运行态

时机:对运行态中的线程进行了如下处理后,线程处于不可运行状态。

²       调用了sleep()方法;

²       调用了suspend()方法(线程被挂起);

²       为等候一个条件变量,线程调用wait()方法;

²       输入输出流中发生线程阻塞。

try {

Thread.currentThread().sleep(10000);

} catch (InterruptedException e){}

线程去向:进入运行态或死亡态。

 

四种不可运行态有不同的方式回到运行态:

sleep()——方法中的参数为睡眠时间,时间一到,线程即为可运行的;

suspend()——调用resume()方法来返回;

wait()——该条件变量所在的对象调用notify()或notifyAll()方法;

在I/O流中发生线程阻塞——待特定的I/O指令结束,自动回到可运行态。

 

死亡态

时机:对线程执行stop()方法,或线程运行结束,即进入死亡态。

说明:可用线程对象=null;代替线程对象.stop();

 

多线程程序设计

由Thread继承类创建线程

程序框架

class NewThread extends Thread{ //对应一个任务

  ……

  public void run(){

 

  }

}

  ……

class ThreadTest{

  public static void main(String[] aa){

     NewThread t=new NewThread();

     t.start();

  ……

  }

}

要点:

²       定义一个继承Thread的子类,重写 run()方法,实现一个子任务。

²       main方法中,实例化子类,创建一个线程对象,并调用start()方法执行子任务。

 

例:阅读和分析程序。

class ThreadTest extends Thread{

  int n=0;

  int count=0;

  ThreadTest(int i){

     count=i;

     start();

  }

  public void run(){

     while(true){

        System.out.print(count+" ");

        if(n++>15)return;

//       yield();

     }

  }

  public static void main(String[] aa){

     for(int i=0;i<5;i++)

        new ThreadTest(i);

  }

}

 

得到结论:

²       主线程的执行位置就是main。

²       每个线程执行其代码的方式都是顺序执行的。

²       一个线程执行其代码与其他线程相互独立。

 

进一步的理解:

²       线程对于cpu时间片的交替占有特性。

²       用yield()方法,让当前线程作出让步。

public static void yield()

²       用sleep()可让线程休眠一定的时刻(毫秒数)。

public static void sleep(long millis)

分析和回答

 

1.    该程序启动了多少个线程?

2.    线程的执行机会是顺序均匀的吗?

3.    让线程作出让步的方法是?

4.    让线程休眠的方法是?

5.    哪条语句决定一个线程运行结束?

6.    ThreadTest的构造方法中执行start意在?

 

用Runnable接口创建线程

Runnable接口继承自Thread,其中只有一个run()方法。

察看Thread的其他构造方法

public  Thread(Runnable target)

public  Thread(Runnable target,String name)

通过Runnable创建线程的步骤:

²       定义一个实现了Runnable的类,其中重写run方法说明任务。

²       通过new Thread(Runnable子类的实例对象)创建线程。

 

练习1:利用Runable改写上例。

 

练习2:用多线程模拟多处卖票的问题(假定总共有100张票,有三个卖票窗口)。

class SellTicket implements Runnable{

  static int num=1;

  public void run(){

 

  }

  public static void main(String[] aa){

 

  }

}

比较:多线程设计时,Runnable接口更具灵活性。一个用户类可以在继承别的类的基础上实现该接口。

思考

1.    该程序启动了多少个线程?

2.    线程的执行机会是顺序均匀的吗?

3.    让线程作出让步的方法是?

4.    让线程休眠的方法是?

5.    哪条语句决定一个线程运行结束?

6.    ThreadTest的构造方法中执行start意在?

 

多线程应用举例

一个时钟Applet示例

设计如下图示的时钟,可以逐秒更新显示。

 

 

设计分析

类Clock继承JApplet,并实现Runnable接口,通过一个线程来实现时间的更新显示。

其中各方法的主要任务:

init()——

paint(Graphics g)——获取当前日历,显示时间

run()——线程的运行(隔一秒刷新)

 

取得系统时间的方法:

Calendar now = Calendar.getInstance();

利用其中的get方法获取HOUR、MINUNT、SECOND

import java.awt.*;

import java.util.*;

import javax.swing.*;

 

public class Click extends JApplet implements Runnable{

  Font f=new Font("",0,50);

  public void init(){

  //创建并运行线程

  }

  public void paint(Graphics g){

     g.clearRect(0,0,this.getWidth(),this.getHeight());

     g.setFont(f);

     g.setColor(Color.green);

     g.drawString(getTime(),50,50);

  }

  public void run(){

  //线程的运行(隔一秒刷新)

  }

  public String getTime(){

     Calendar now=Calendar.getInstance();

     //取得系统时间

  }

}

 

练习:将上例改写为Java Application。

 

一个飞行动画Applet

 

飞船图片文件名:Rocketship.gif

 

线程的优先级和线程调度

单CPU的计算机在执行多线程程序时需进行线程调度。

线程优先级的设置

public final void setPriority(int newPriority)

参数:可选线程中的三个静态变量

²       MAX_PRIORITY(高优先级)10

²       MIN_PRIORITY(低优先级)1

²       NORM_PRIORITY(缺省的优先级)5

Java采用的是抢占式的调度方式,高优先级的线程进入可运行(runnable)状态时,会抢占低优先级的线程的位置。

 

例:观察下列程序的执行情况,回答问题。

class ThreadTest{

  public static void main(String[] aa){

     MyThread mt1=new MyThread();

     MyThread mt2=new MyThread();

     mt1.setPriority(5);

     mt2.setPriority(10);

     mt1.start();

     mt2.start();

     while(true){

        System.out.println(Thread.currentThread().getName());

     }    

  }

}

class MyThread extends Thread{

  public void run(){

     while(true){

        System.out.println(this.getName());

     }    

  }

}

问题:

高优先级线程会绝对占据CPU资源吗?

观察程序运行结果,体会线程优先级的意义

 

线程的同步

以上所创建和运行的线程都是独立的,而且异步执行:

²       每个线程自含了运行所需要的数据或方法,不需要外部的资源或方法

²       每个线程不关心其它线程的状态或行为。

 

为什么要同步?

问题:有两个线程thread1、thread2,当它们操作同一个对象时,会发现由于thread1与thread2是同时执行的,因此可能thread1修改了数据而thread2读出的仍为旧数据。

问题原因:资源使用协调不当(不同步)造成的。

解决方法:协调线程,以免共享资源时,线程间发生冲突。——同步!

 

同步的实现方法

Java提供了同步方法和同步状态来协调资源:

²       同步方法指方法前修饰synchronized关键字

²       同步状态指对象或数据前修饰synchronized关键字

²       被宣布为同步的方法、对象或类数据,在任一时刻只能被一个线程使用

 

同步方法

同步方法:用synchronized修饰的方法

作用:同步方法当一个线程执行时自动加锁,直到方法运行结束锁解除。

例:阅读程序,理解同步方法

class SynchTest implements Runnable{

  public static void main(String args[]){

     SynchTest st=new SynchTest();

     Thread t1=new Thread(st);

     Thread t2=new Thread(st);

     t1.start();

     t2.start();

     while((t1.isAlive())||(t2.isAlive()));

     System.out.println("The test is end.");

  }

  public void run(){

     String name=Thread.currentThread().getName();

     show(name);

     System.out.println(name+" is dead!");

  }

  private  synchronized  void show(String name){

     for(int j=1;j<=10;j++){

        System.out.println("I am "+name+"-- I have run "+j+" times.");

     }

  }

}

 

关键字synchronized会告诉JVM在方法运行时需要一个锁,然后 JVM会创建并管理锁:

²       线程可自动请求锁

²       除非某个线程已经申请并获准使用锁,否则很容易申请到锁

²       当同步方法结束后会自动释放锁

 

同步块

同步块:用synchronized(Object)标识的语句块。

作用:语句块被一个线程执行时会自动加锁,直到运行结束,锁解除。

 

posted on 2007-07-21 14:28  丁保国  阅读(678)  评论(0)    收藏  举报