疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java多线程编程初步


 

  一、什么是多线程编程

    多线程编程技术是Java语言的重要特点。多线程编程的含义是将程序任务分成几个并行的子任务。特别是在网络编程中,你会发现很多功能是可以并发执行的。比如网络传输速度较慢、用户输入速度较慢,你可以用两个独立的线程去完成这两个功能,而不影响正常的显示或其它功能。

    多线程是与单线程比较而言的,普通的Windows采用单线程程序结构,其工作原理是:主程序有一个消息循环,不断从消息队列中读入消息来决定下一步所要干的事情,一般是针对一个函数,只有等这个函数执行完之后,主程序才能接收另外的消息来执行。比如子函数功能是在读一个网络数据,或读一个文件,只有等读完这个数据或文件才能接收下一个消息。在执行这个子函数过程中你什么也不能干。但往往读网络数据和等待用户输入有很多时间处于等待状态,多线程利用这个特点将任务分成多个并发任务后,就可以解决这个问题。

    二、Java中的线程类

    要学会Java中的多线程编程,就得知道如何实现支持多线程的类。Java中实现多线程的类有两种方法:

    1.扩展java.lang.Thread类,用它覆盖Thread类的run方法。

    2.生成实现java.lang.Runnable接口的类并将其它的实例与java.lang.Thread实例相关联。

    Thread类是负责向其它类提供线程支持的最主要的类,要使用一个类具有线程功能,在Java中只要简单地从Thread类派生一个子类就可以了扩展Thread类,如printThread.java.

    下面我们将通过实例来介绍如何使用这两种方法编写自己的多线程应用程序。

    三、创建java.lang.Thread类的子类

    Thread类最重要的方法是run方法。run方法是新线程执行的方法,因此生成java.lang.Thread的子类时,必须有相应的run方法。

    //PrintThread.java

    public class PrintThread extends Thread//继承Tread类

    private int count=0 //定义一个count变量用于统计打印的次数并共享变量

    public static void mainString args //main方法开始

    PrintThread p=new PrintThread //创建一个线程实例

    p.start //执行线程

    for{;;} //主线程main方法执行一个循环,for执行一个死循环

    count++

    System.out.printcount+″:Main ″ //主线程中打印count +“main”变量的值,并换行

    public void run //线程类必须有的run()方法

    for{;;}

    count++

    System.out.printcount+″:Thread ″ 

    上面这段程序便是继承java.lang.Tread并覆盖run的方法。用Java 虚拟机启动程序时,这个程序会先生成一个线程并调用程序主类的main方法。这个程序中的main方法生成新线程,连接打印“Thread”。在启动线程之后,主线程继续打印“Main”。

    编译并执行这个程序,然后立即按“Ctrl+C”键中断程序,你会看到上面所述的两个线程不断打印出:XXX:main………XXX:Thread…。 XXX代表的是数字,也就是上面count的值 。在笔者的机器上,不同时刻这两个线程打印的次数不一样,先打印20个main(也就是先执行20次主线程)再打印出50次Thread,然后再打印main……

    提示:为了便于查看该程序的执行结果,你可以将执行结果导入一个文本文件,然后打开这个文件查看各线程执行的情况。如运行:

    javac PrintThread.java

    Java PrintThread>1.txt

    第一个命令javac PrintThread.java是编译java程序,第二个是执行该程序并将结果导入1.txt文件。这样,打开这个文件,你就可以看见详细的结果了(注意:程序的执行时间不能太长,不然生成的1.txt文件会很庞大)。当然你可以直接执行命令:java PrintThread.

    四、实现java.lang.Runnable接口

    运行线程的另一种方法是实现Runnable接口,然后生成运行这个类的线程即可。Runnable接口是定义在java.lang包中的一个接口,其中只提供了一个抽象的run声明。

    下面我们来看看如何实现Runnable接口,而不是扩展Thread类。

    //PrintRunnableThread.java 实现Runnable接口

    public class PrintRunnableThread implements Runnable

    public static void mainString args 

    Thread t=new Threadnew PrintRun

    nableThread  

    //t.setPriorityThread.MAX_PRIORI

    TY //设置最大优先级

    t.start //线程开始

    for{;;} //不停地打印字符M,代表主线程main

    System.out.println″M″ 

    public void run 

    for{;;} //不停地打印字符T,代表线程thread

    System.out.println″T″ 

    运行本程序,你会发现执行结果和PrintThread.java的执行结果很类似,甚至你可以再添加一个变量,并打印出相似的线程执行结果。

    提示:与前例不同的是,如果去掉t.setPriorityThread.MAX_PRIORITY 语句前的注释符,就将线程设置成最大优先级,执行结果就大不一样了。

    五、线程组ThreadGroup

    java.lang.ThreadGroup类表示一组线程(可能包含其它ThreadGroup),用来实现按照特定功能对线程进行集中式分组管理。用户创建的每个线程均属于某线程组,这个线程组可以在线程创建时指定,也可以不指定线程组以使该线程处于默认的线程组之中。但是,一旦线程加入某线程组,该线程就一直存在于该线程组中直至线程终止,不能在中途改变线程所属的线程组。

    下面的代码演示了如何操作和使用ThreadGroup.

    //ThreadGroupTest.java

    public class ThreadGroupTest implements Runnabl

    e

    public void run 

    public static void main(String args[])

    //生成一个新的线程组,并将两个线程对象放到该线程组里。

    ThreadGroup threadgroup=new ThreadGroup″线程组″ 

    Thread t1=new Threadthreadgroupnew ThreadGrou

    pTest ″线程 1″ 

    Thread t2=new Threadthreadgroupnew ThreadGrou

    pTest ″线程 2″ 

    //找到顶级的父线程

    ThreadGroup parent=Thread.currentThread 。getThrea

    dGroup //得到当前线程的线程组

    whileparent.getParent =null

    parent=parent.getParent //得到父线程

    /st 方法打印出当前线程组的所有内容线程和子线程组

    parent.list 

    提示:上段程序中,list()方法显示ThreadGroup树(可能包括线程和线程组,所以构成了线程树)的结构和内容。运行上面程序你就会对线程组有了一定的了解。

    六、线程优先级

    虽然我们说线程是并发运行的。然而事实常常并非如此。当系统中只有一个CPU时,以某种顺序在单CPU情况下执行多线程被称为调度scheduling 。Java采用的是一种简单、固定的调度法,即固定优先级调度。这种算法是根据处于可运行线程的相对优先级来实行的。当线程产生时,它继承原线程的优先级。在需要时可对优先级进行修改。在任何时刻,如果有多条线程等待运行系统选择优先级最高的可运行线程运行。只有当它停止、自动放弃、或由于某种原因成为非运行状态优先级的线程时才能运行。如果两个线程具有相同的优先级它们将被交替地运行。

    Java中的第一个线程都有优先级,线程的优先级是介于Thead.MIN_PRIORITY到Thread.MAX_PRIORITY之间的整数介于0到10之间 。缺省情况下,线程的优先级是5即NORM_PRIORITY 。我们可以用形如Thread.setPriorityThread.MIN_PRIORITY 这样的表达式来设置线程的优先级稍后会在例程中用到 。也可以通过getPriority 来得到线程的优先级,还可以通过setPriority 在线程创建之后的任意时间改变线程的优先级。

    提示:当线程中的代码创建一个新线程对象时,这个新线程拥有与创建它的线程一样的优先级。

    七、线程的管理

    单线程的程序都有一个main执行体,它运行一些代码,当程序结束执行后,程序结束运行。在Java中我们要得到相同的应答,必须稍微进行改动。只有当所有的线程退出后,程序才能结束。只要有一个线程一直在运行,程序就无法退出。

    线程包括new()开始、running()运行、wait()等候和done结束执行状态。第一次创建线程时,都位于new状态,在这个状态下,不能运行线程,只能等待。这时,线程要么调用start方法开始运行,要么送往done状态结束。位于done中的线程已经结束执行,这是线程的最后一个状态。一旦线程位于这个状态,就不能再次出现,而且当Java虚拟机中的所有线程都位于done状态时,程序就强行中止。

    当前正在执行的所有线程都位于running状态,在程序之间用某种方法把处理器的执行时间分成时间片。位于running状态的每个线程都是能运行的,但在一个给定的时间内,每个系统处理器只能运行一个线程。与位于running状态的线程不同,由于某种原因,可以把已经位于waiting状态的线程从一组可执行线程中删除。如果线程的执行被中断,就回到waiting状态。这时,线程可能被挂起,在系统资源上等候,或者被告知进入休眠状态Sleep 。该状态的线程可以返回到running状态,也能由stop送入done状态。Thread类提供了Sleep()、Stop、Yield()、Suspend()和Resume()等方法来管理线程。

    八、线程操作的其它概念

    通过这期两个程序的学习,可能你会认为Java的线程操作很简单,但实事并非如此。在实际工作中,可能需要综合考虑很多问题,比如设置监控线程、暂停、命名和协调线程、设置线程的优先级、共享变量、线程同步、线程池、线程组等。对于这些线程相关的操作,笔者不在本章中详细讲述了。如果读者有兴趣,可参考相应的专著,毕竟这部分不是一两篇文章可以完全讲解完的。