什么是线程?
线程的概念并不难于掌握它是程序代码的一个独立的执行通道当多个线程执行时经由相同代码的一个线程的通道通常与其它的不同例如假设一个线程执行一段相当于一个ifelse语句的if部分的字节代码时而另一个线程正执行相当于else部分的字节代码JVM怎样保持对于每一个线程执行的跟蹤呢?JVM给每一个线程它自己的方法调用堆栈另外跟蹤当前指令字节代码方法堆栈跟蹤本地变量JVM传递给一个方法的参数以及方法的返回值
当多个线程在同一个程序中执行字节代码序列时这种行为叫作多线程多线程在多方面有利于程序
·当执行其它任务时多线程GUI(图形用户界面)程序仍能保持对用户的响应比如重编页码或打印一个文档
·带线程的程序一般比它们没有带线程的副本程序完成得快这尤其表现在线程运行在一个多处理器机器上在这里每一个线程都有它自己的处理器
Java通过javalangThread类完成多线程每一个线程对象描述一个单独的执行线程那些运行发生在线程的run()方法中因为缺省的run()方法什么都不做你必须创建Thread子类并重载run()以完成有用的工作练习列表中领略一个在Thread中的线程及多线程
列表 ThreadDemojava
// ThreadDemojava
class ThreadDemo
{
public static void main (String [] args)
{
MyThread mt = new MyThread ();
mtstart ();
for (int i = ; i < ; i++)
Systemoutprintln (i = + i + i * i = + i * i);
}
}
class MyThread extends Thread
{
public void run ()
{
for (int count = row = ; row < ; row++ count++)
{
for (int i = ; i < count; i++)
Systemoutprint (*);
Systemoutprint (\n);
}
}
}
列表显示了一个由类ThreadDemo和MyThread组成的应用程序的源代码类ThreadDemo通过创建一个MyThread对象驱动应用程序开始一个与其对象相关的线程并执行一段打印一个正方形表的代码相反 MyThread重载Thread的run()方法打印(通过标准输入流)一个由星形符号组成的直角三角形
当你键入java ThreadDemo运行应用程序时 JVM创建一个运行main()方法的开始线程通过执行mtstart ()开始线程告诉JVM创建一个执行包含MyThread对象的run()方法的字节代码指令的第二个线程当start()方法返回时开始线程循环执行打印一个正方形表此时另一个新线程执行run()方法打印直角三角形
输出会象什么样呢?运行ThreadDemo就可以看到你将注意到每一个线程的输出与其它线程的输出相互交替这样的结果是因为两个线程将它们的输出都发送到了同样的标准输出流
注意
多数(不是所有)JVM设备使用下层平台的线程性能因为那些性能是平台特有的你的多线程程序的输出顺序可能与一些人的其他输出的顺序不一样这种不同是由于时序的安排我将在这一系列的稍后探讨这一话题
线程类
要精通写多线程代码你必须首先理解创建Thread类的多种方法这部份将探讨这些方法明确地说你将学到开始线程的方法命名线程使线程休眠决定一个线程是否激活将一个线程与另一个线程相联和在当前线程的线程组及子组中列举所有激活的线程我也会讨论线程调试辅助程序及用户线程与监督线程的对比
我将在以后的文章中介绍线程方法的余下部份Sun不赞成的方法除外
警告
Sun有一些不赞成的线程方法种类比如suspend()和resume()因为它们能锁住你的程序或破坏对象所以你不必在你的代码中调用它们考虑到针对这些方法工作区的SDK文件在这篇文章中我没有包含这些方法
构造线程
Thread有八个构造器最简单的是
·Thread()用缺省名称创建一个Thread对象
·Thread(String name)用指定的name参数的名称创建一个Thread对象
下一个最简单的构造器是Thread(Runnable target)和Thread(Runnable target String name) 除Runnable参数之外这些构造器与前述的构造器一样不同的是Runnable参数识别提供run()方法的线程之外的对象(你将在这篇文章稍后学到Runnable)最后几个构造器是Thread(String name)Thread(Runnable target)和Thread(Runnable target String name)然而最后的构造器包含了一个为了组织意图的ThreadGroup参数
最后四个构造器之一Thread(ThreadGroup group Runnable target String name long stackSize)令人感兴趣的是它能够让你指定想要的线程方法调用堆栈的大小能够指定大小将证明在使用递归方法(一种为何一个方法不断重复调用自身的技术)优美地解决一些问题的程序中是十分有帮助的通过明确地设置堆栈大小你有时能够预防StackOverflowErrors然而太大将导致OutOfMemoryErrors同样Sun将方法调用堆栈的大小看作平台依赖依赖平台方法调用堆栈的大小可能改变因此在写调用Thread(ThreadGroup group Runnable target String name long stackSize)代码前仔细考虑你的程序分枝
开始你的运载工具
线程类似于运载工具它们将程序从开始移动到结束Thread 和Thread子类对象不是线程它们描述一个线程的属性比如名称和包含线程执行的代码(经由一个run()方法)当一个新线程执行run()时另一个线程正调用Thread或其子类对象的start()方法例如要开始第二个线程应用程序的开始线程—它执行main()—调用start()作为响应JVM和平台一起工作的线程操作代码确保线程正确地初始化并调用Thread或其子类对象的run()方法
一旦start()完成多重线程便运行因为我们趋向于在一种线性的方式中思维我们常发现当两个或更多线程正运行时理解并发(同时)行为是困难的因此你应该看看显示与时间对比一个线程正在哪里执行(它的位置)的图表下图就是这样一个图表
与时间对比一个开始线程和一个新建线程执行位置的行为
图表显示了几个重要的时间段
·开始线程的初始化
·线程开始执行main()瞬间
·线程开始执行start()的瞬间
·start()创建一个新线程并返回main()的瞬间
·新线程的初始化
·新线程开始执行run()的瞬间
·每个线程结束的不同瞬间
注意新线程的初始化它对run()的执行和它的结束都与开始线程的执行同时发生
警告
一个线程调用start()后在run()方法退出前并发调用那方法将导致start()掷出一个javalangIllegalThreadStateException对象
怎样使用名称
在一个调试会话期间使用用户友好方式从另一个线程区别其中一个线程证明是有帮助的要区分其中一个线程Java给一个线程取一个名称Thread缺省的名称是一个短线连字符和一个零开始的数字符号你可以接受Java的缺省线程名称或选择使用你自己的为了能够自定义名称Thread提供带有name参数和一个setName(String name)方法的构造器Thread也提供一个getName()方法返回当前名称表显示了怎样通过Thread(String name)创建一个自定义名称和通过在run()方法中调用getName()检索当前名称
表NameThatThreadjava
// NameThatThreadjava
class NameThatThread
{
public static void main (String [] args)
{
MyThread mt;
if (argslength == )
mt = new MyThread ();
else
mt = new MyThread (args []);
mtstart ();
}
}
class MyThread extends Thread
{
MyThread ()
{
//编译器创建等价于super()的字节代码
}
MyThread (String name)
{
super (name); //将名称传递给Thread超类
}
public void run ()
{
Systemoutprintln (My name is: + getName ());
}
}
你能够在命令行向MyThread传递一个可选的name参数例如java NameThatThread X 建立X作为线程的名称如果你指定一个名称失败你将看到下面的输出
My name is: Thread
如果你喜欢你能够在MyThread(String name)构造器中将super(name)调用改变成setName(String name)调用——作为setName(name)后一种方法调用达到同样建立线程名称的目的——作为super(name)我作为练习保留给你们
注意
Java主要将名称指派给运行main() 方法的线程开始线程你特别要看看当开始线程掷出一个例外对象时在线程main的例外显示的JVM的缺省例外处理打印消息
休眠或停止休眠
在这一栏后面我将向你介绍动画——在一个表面上重复画图形这稍微不同于完成一个运动画面要完成动画一个线程必须在它显示两个连续画面时中止调用Thread的静态sleep(long millis)方法强迫一个线程中止millis毫秒另一个线程可能中断正在休眠的线程如果这种事发生正在休眠的线程将醒来并从sleep(long millis)方法掷出一个InterruptedException对象结果调用sleep(long millis)的代码必须在一个try代码块中出现——或代码方法必须在自己的throws子句中包括InterruptedException
为了示范sleep(long millis)我写了一个CalcPI应用程序这个应用