现在的位置: 首页 > 综合 > 正文

Java第十次课-多线程

2013年11月09日 ⁄ 综合 ⁄ 共 9007字 ⁄ 字号 评论关闭

 第8章
多线程

本章目录
8.1 并发性、线程与多线程
8.2 获得线程体的两种方法
8.3 线程调度

8.1 并发性、线程与多线程
一、并发性的概念
目前的计算机操作系统大多支持并发性,即多个进程是交叉执行的,一般称多进程为系统级并发。
Java语言通过程序控制流来执行程序,单个的一段程序执行控制流称为线程。
多线程指的是在单个程序内部可以同时运行多个相同或不同的线程来执行不同的任务。
线程与操作系统中的进程有些相似,同一时刻操作系统中可以有多个进程在运行,但是线程则更进一步,将并发推进到语言级,所以说Java语言支持语言级并发。
在单个程序内部也可以在同一时刻有多个线程在进行不同的运算。
多线程也需要系统通过分配处理器的运行时间进行调度以提高整个程序的运行效率。

二、线程的状态与生命周期

一个线程在其生命生存时段内的任一时刻都处于某一种线程状态,线程在从其生命开始到结束的时间内将可能经历从出生态到死亡态的多种状态,这构成了它的生命周期。
线程的状态包括以下7种:
出生态(Born state),也称为新线程(New Thread)。
就绪态(Ready state),也称为可运行线程(Runnable Thread)。
运行态(Running state)。
休眠态(Sleeping state)。
等待态(Waiting state)。
阻塞态(Blocked state)。
死亡态(Dead state)。
特别的线程关系:两个线程的“依赖” 关系。
8.2 获得线程体的两种方法
线程从根本上讲是一段程度代码序列,还需要在程序中实现。
java.lang包中定义了Thread类,是Java语言多线程程序设计的基础和关键。
Thread类是java.lang.Object类的直接子类,其中定义的一些方法成员完成与线程有关的操作:
interrupt()                        //中断线程
join()                             //等待该线程终止
join(long millis)                  //等待该线程终止,在指定的毫秒数内
join(long millis, int nanos)       //等待该线程终止,在指定的毫秒数加指定的纳秒数内
sleep(long millis)                 //休眠当前正在执行的线程,在指定的毫秒数内
sleep(long millis, int nanos)       //休眠当前正在执行的线程,在指定的毫秒数加指定的纳秒数内
start()                            //使该线程开始执行
yield()                            //暂停当前正在执行的线程,并执行其他线程
notify()                           //唤醒在此对象监视器上等待的单个线程
notifyAll()                        //唤醒在此对象监视器上等待的所有线程
wait()                             //导致当前的线程等待
wait(long timeout)                 //导致当前的线程等待timeout毫秒
wait(long timeout, int nanos)      //导致当前的线程等待timeout毫秒加nanos纳秒
isAlive()                          //测试线程是否处于活动状态
isInterrupted()                    //测试线程是否已经中断
getPriority()                      //返回线程的优先级
getId()                            //返回线程的标识符
getName()                          //返回线程的名称
getState()                         //返回线程的状态
setPriority(int newPriority)       //更改线程的优先级
setName(String name)               //改变线程名称,使之与参数name相同
Java语言支持多线程,线程在Application程序和Applet程序中都可以使用,在Applet中使用得更普遍一些。
线程的行为由线程体确定,线程体是线程的主体,含有线程的具体内容。
Java语言程序中实现线程的程序设计的关键是使主程序获得run()方法并重写run()方法,重写了run()方法也就定义了线程体。
根据获得run()方法的途径不同,定义线程体有两种完全等价的方式。
一、通过继承Thread类获得线程体
在java.lang.Thread类中定义有run()方法,可以在当前程序中通过继承Thread类间接继承run()方法,并在程序中重写该方法而构造出线程体。
二、通过实现Runnable接口获得线程体
在java.lang包中定义有一个接口Runnable,其中只有一个run()方法,可以在当前程序中实现该接口并重写该方法而构造出线程体。这种方法比较多见于编写Applet程序,当一个

Applet程序的线程体必须要在主类中实现的时候,就别无选择地要使用实现Runnable接口的方法,与前一种方法相比,这种方法的效果是一样的。
多个线程同时在一个程序中执行,要通过系统的线程调度获得处理器才能执行,所以完全可能出现一个线程还没有执行完就被迫停下来而由另一个线程取而代之获得处理器的情况

,所以就会出现同一个线程所执行的内容的顺序是固定的,而多个线程的执行内容的顺序就可能出现不同的组合的结果,这种现象称为线程的不确定性。
//Example 1 of Chapter 8

import java.awt.*;
import javax.swing.*;

public class MultiThreadDemo1 extends JFrame
{
 private ScrollPane scrollPane;
 private JTextArea area;
 
 public MultiThreadDemo1()
 {
  super( "多线程输出演示" );
  getContentPane().setLayout( new BorderLayout() );
  scrollPane = new ScrollPane();
  area = new JTextArea();
  area.setEditable( false );
  scrollPane.add( area );
  getContentPane().add( scrollPane, BorderLayout.CENTER );
  
  OutputThread no1 = new OutputThread( "Number1" );
  OutputThread no2 = new OutputThread( "Number2" );
  OutputThread no3 = new OutputThread( "Number3" );
  
  area.append( "主程序启动/n" );
  
  no1.start();
  no2.start();
  no3.start();
  
  area.append( "主程序结束/n" );
  
  setSize( 300, 240 );
  setVisible( true );
 }
 
 public static void main(String[] args)
 {
  MultiThreadDemo1 demo = new MultiThreadDemo1();
  demo.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 }
 
 private class OutputThread extends Thread
 {
  private int sleepTime;
  
  public OutputThread( String name )
  {
   super( name );
  }
  
  public void run()
  {
   try {
    sleepTime = ( int ) ( Math.random() * 2000 );
    area.append( getName() + ":即将休眠" + sleepTime + "毫秒/n" );
    Thread.sleep( sleepTime );
    sleepTime = ( int ) ( Math.random() * 2000 );
    area.append( getName() + ":即将再次休眠" + sleepTime + "毫秒/n" );
    Thread.sleep( sleepTime );
   }
   catch ( InterruptedException exception )
   {
    exception.printStackTrace();
   }
   area.append( getName() + ":结束运行/n" );
  }
 }
}
8.3 线程调度
一、线程的优先级
每个线程都有自己的优先级。当Java线程被创建时,该线程从父线程中继承优先级并保持一致。线程被创建后,可以改变线程的优先级。
线程的优先级定义为10级,分别用数字1到10表示。

在Thread类中还定义了几个描述线程的优先级的字段:
最高优先级MAX_PRIORITY对应10级,
默认优先级NORM_PRIORITY对应5级,
最低优先级MIN_PRIORITY对应1级。
//Example 2 of Chapter 8

import java.awt.*;
import javax.swing.*;

public class MultiThreadDemo2 extends JFrame
{
 private ScrollPane scrollPane;
 private JTextArea area;
 
 public MultiThreadDemo2()
 {
  super( "多线程输出演示" );
  getContentPane().setLayout( new BorderLayout() );
  scrollPane = new ScrollPane();
  area = new JTextArea();
  area.setEditable( false );
  scrollPane.add( area );
  getContentPane().add( scrollPane, BorderLayout.CENTER );
  
  OutputThread no1 = new OutputThread( "Number1" );
  OutputThread no2 = new OutputThread( "Number2" );
  OutputThread no3 = new OutputThread( "Number3" );
  no1.start();
  no2.start();
  no3.start();
  
  setSize( 240, 400 );
  setVisible( true );
 }
 
 public static void main(String[] args)
 {
  MultiThreadDemo2 demo = new MultiThreadDemo2();
  demo.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 }
 
 private class OutputThread extends Thread
 {
  public OutputThread( String name )
  {
   super( name );
  }
  
  public void run()
  {
   for(int i=5; i>0; i--)area.append( "( "+i+" )这是线程" + getName() + "/n" );
   area.append( "退出线程" + getName() + "/n" );
  }
 }
}

线程的优先级的作用是方便操作系统调度线程,操作系统总是让优先级高的线程先于优先级低的线程执行。
对于优先级相同的线程,在不采用分时技术的操作系统中总是让一个线程一直运行直到完成任务,除非它自己转化为休眠态、等待态或阻塞态;在采用分时技术的操作系统中每个

线程都将获得称为时间片的处理器运行时间以使线程能够运行。

可以通过调用Thread类的getPriority()方法获取线程的优先级,setPriority()方法更改线程的优先级。
//Example 3 of Chapter 8

import java.awt.*;
import javax.swing.*;
import java.applet.Applet;
import java.util.Date;

public class MultiThreadDemo3 extends Applet implements Runnable
{
 Thread clockThread;
 
 public  void  start()
 {
  if(clockThread==null)
  {
   clockThread = new Thread( this, "Clock" );
   clockThread.start();
  }
 }
 
 public void run()
 {
  while(clockThread!=null)
  {
   repaint();
   try{
    clockThread.sleep(1000);
   }
   catch ( InterruptedException exception )
   {
    exception.printStackTrace();
   }
  }
 }
 
 public void paint( Graphics g )
 {
  Date now = new Date();
  g.setFont( new Font( "Dialog", Font.PLAIN, 60 ) );
  g.drawString( now.getHours() + ":" + now.getMinutes() +
   ":" + now.getSeconds() , 5 , 80);
 }
 
 public void stop()
 {
  clockThread.stop();
  clockThread = null;
 }
}
二、线程同步
线程在访问共享对象时不允许其它线程访问该对象的情况称为线程互斥或线程同步。线程同步与计算机操作系统中的进程同步很相似。
Java语言采用监控器机制来实现线程同步,监控器机制也称为获得锁。

在Java语言中为每个对象都设置了一个监控器,监控器每次只允许一个线程来执行对象的同步语句,当程序控制流进入同步语句时,将对象锁住,从而实现线程同步。
任意一个时刻如果有多条语句试图使对象同步则只能有一条同步语句被激活,其它所有试图对同一对象实现同步的线程都将被迫处于阻塞态。
当实现线程同步的语句完成执行过程之后,监控器才会打开对象的锁并按照线程调度原则处理其它的同步语句,分配处理器时间。

在Java语言程序设计中,可以采用“synchronized
”关键字实现同步方法,具体用法就是在程序中进行方法定义时用“synchronized
”关键字说明方法,使得方法具有同步属性,任一时刻只有一个线程能够调用带有同步属性的方法。
还可以在程序中使用wait()方法使一个暂时没有获得完全的条件,无法对该对象继续执行任务的线程进入等待态。

当一个线程完成了自己的同步语句并执行完自己的代码使得其它的线程所等待的条件得以满足之后,也可以通过使用notify()方法将一个正处于等待态的线程再次转为就绪态。也

可以通过使用notifyAll()方法将所有处于等待态的线程再次转为就绪态。
转为就绪态的线程都有机会获得对象的锁,但是任一时刻最多只能有一个线程获得对象的锁,其余的线程都将处于阻塞态。

有几个问题是在编写线程同步程序时应该注意的:
 ⑴当对象的锁被释放时,阻塞线程调用一个用“synchronized”关键字说明的方法并不保证一定就能立刻成为下一个获得锁的线程。
 ⑵调用了监控器的wait()方法成为等待态的线程经由其它线程调用notify()方法之后并不保证一定会脱离等待态。
 ⑶在同步方法中建议在wait()方法之前调用notifyAll()方法唤醒所有等待态线程,包括该线程自身,而将同步线程的控制选择权交由标记变量控制。

 ⑷使用
while(!条件)……wait()……notify()
结构来完成同步方法的定义,这样做要比使用
if(!条件)……wait()……notify()
结构来完成定义要安全。
 ⑸不要在线程同步的程序中调用sleep()方法,这样做通常是错误的。
 ⑹wait()方法通常要抛出中断异常InterruptedException,所以在wait()方法外部要进行捕获和处理异常的操作。

//Example 4 of Chapter 8

import java.awt.*;
import javax.swing.*;

public class MultiThreadDemo4 extends JFrame
{
 private ScrollPane scrollPane;
 private JTextArea area;
 
 public MultiThreadDemo4()
 {
  super( "线程同步演示" );
  getContentPane().setLayout( new BorderLayout() );
  scrollPane = new ScrollPane();
  area = new JTextArea();
  area.setEditable( false );
  scrollPane.add( area );
  getContentPane().add( scrollPane, BorderLayout.CENTER );
  
  DataProcesse dataprocesser = new DataProcesse();
  Provider provider = new Provider( dataprocesser );
  Comparer comparer = new Comparer( dataprocesser );
  provider.start();
  comparer.start();
  
  setSize( 240, 400 );
  setVisible( true );
 }
 
 public static void main(String[] args)
 {
  MultiThreadDemo4 demo = new MultiThreadDemo4();
  demo.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
 }
 
 private class Provider extends Thread
 {
  private DataProcesse processer;
  public Provider( DataProcesse dp )
  {
   super( "提供者" );
   processer = dp;
  }
  
  public void run()
  {
   for ( int i = 0; i <= 4; i++ )
   {
    processer.set( i );
   }
   area.append( getName() + "提供完数据/n" );
  }
 }
 
 private class Comparer extends Thread
 {
  private DataProcesse processer;
  public Comparer( DataProcesse dp )
  {
   super( "处理者" );
   processer = dp;
  }
  
  public void run()
  {
   double sum = -30.0;
   for ( int i = 0; i <= 4; i++ )
   {
    double a = processer.get();
    if(sum < a)sum = a;
   }
   area.append( getName() + "读到的最大数据是" + sum + "/n" );
  }
 }
 
 class DataProcesse
 {
  private double buffer;
 private boolean flag = false;
  public synchronized void set( double value )
  {
   notifyAll();
   while ( flag == true )
   {
    try{
    area.append( "缓存中有数据," + Thread.currentThread().getName() + "等待/n" );
    wait();
    }
    catch ( InterruptedException exception )
    {
     exception.printStackTrace();
    }
   }
   buffer = value;
   flag = true;
   area.append( Thread.currentThread().getName() + "写入" + buffer + "/n" );
   notify();
  }
  
  public synchronized double get()
  {
   notifyAll();
   while ( flag == false )
   {
    try{
    area.append( "缓存中无新数据," + Thread.currentThread().getName() + "等待/n" );
    wait();
    }
    catch ( InterruptedException exception )
    {
     exception.printStackTrace();
    }
   }
   flag = false;
   area.append( Thread.currentThread().getName() + "读出" + buffer + "/n" );
   notify();
   return buffer;
  }

抱歉!评论已关闭.