runnable线程:用Java线程获取优异性能(I)——介绍线程、线程类及Runnable来源: 发布时间:星期三, 2008年12月17日 浏览:2次 评论:0
用Java线程获取优异性能(I)
摘要
用户期望能展现优异性能为了满足这个期望你常常使用到线程在这篇文章中我们开始练习使用线程你将学习到线程、线程类及Runnable
用户不喜欢反应迟钝软件Software当用户单击个鼠标时他们希望立即回应他们请求即使正处于费时运行的中比如为篇很长文档重编页码或等待个网络操作完成对用户响应很慢其性能拙劣为提高性能开发者般使用线程
这篇文章是探索线程第部份虽然你可能认为线程是种难于掌握事物但我打算向你显示线程是易于理解在这篇文章中我将向你介绍线程和线程类以及讨论Runnable此外在后面文章中我将探索同步(通过锁)同步问题(比如死锁)等待/通知机制时序安排(有优先权和没有优先权)线程中断计时器挥发性线程组和线程本地变量
阅读有关线程设计整个系列:
·第1部份:介绍线程和线程类以及Runnable
·第2部份:使用同步使线程串行化访问关键代码部份
注意
这篇文章及其应用 3个相关线程练习和applets区别然而我在应用中介绍多数应用到applets主要区别是:为了安全原因不是所有线程操作都可以放到个applet中(我将在以后文章中讨论applets)
什么是线程?
线程概念并不难于掌握:它是代码个独立执行通道当多个线程执行时经由相同代码个线程通道通常和其它区别例如假设个线程执行段相当于个-语句部分字节代码时而另个线程正执行相当于部分字节代码JVM怎样保持对于每个线程执行跟踪呢?JVM给每个线程它自己思路方法堆栈另外跟踪当前指令字节代码思路方法堆栈跟踪本地变量JVM传递给个思路方法参数以及思路方法返回值
当多个线程在同个中执行字节代码序列时这种行为叫作多线程多线程在多方面有利于:
·当执行其它任务时多线程GUI(图形用户界面)仍能保持对用户响应比如重编页码或打印个文档
·带线程般比它们没有带线程副本完成得快这尤其表现在线程运行在个多处理器机器上在这里每个线程都有它自己处理器
Java通过java.lang.Thread类完成多线程每个线程对象描述个单独执行线程那些运行发生在线程run思路方法中缺省run思路方法什么都不做你必须创建Thread子类并重载run以完成有用工作练习列表1中领略个在Thread中线程及多线程:
列表1. ThreadDemo.java
// ThreadDemo.java
ThreadDemo
{
public void (String args)
{
MyThread mt = MyThread ;
mt.start ;
for ( i = 0; i < 50; i)
.out.prln ("i = " + i + ", i * i = " + i * i);
}
}
MyThread extends Thread
{
public void run
{
for ( count = 1, row = 1; row < 20; row, count)
{
for ( i = 0; i < count; i)
.out.pr ('*');
.out.pr ('\n');
}
}
}
列表1显示了个由类ThreadDemo和MyThread组成应用源代码类ThreadDemo通过创建个MyThread对象驱动应用开始个和其对象相关线程并执行段打印个正方形表代码相反 MyThread重载Threadrun思路方法打印(通过标准输入流)个由星形符号组成直角 3角形
当你键入java ThreadDemo运行应用时 JVM创建个运行思路方法开始线程通过执行mt.start 开始线程告诉JVM创建个执行包含MyThread对象run思路方法字节代码指令第 2个线程当start思路方法返回时开始线程循环执行打印个正方形表此时另个新线程执行run思路方法打印直角 3角形
输出会象什么样呢?运行ThreadDemo就可以看到你将注意到每个线程输出和其它线程输出相互交替这样结果是两个线程将它们输出都发送到了同样标准输出流
注意
多数(不是所有)JVM设备使用下层平台线程性能那些性能是平台特有你多线程输出顺序可能和些人其他输出顺序不样这种区别是由于时序安排我将在这系列稍后探讨这话题
线程类
要精通写多线程代码你必须首先理解创建Thread类多种思路方法这部份将探讨这些思路方法明确地说你将学到开始线程思路方法命名线程使线程休眠决定个线程是否激活将个线程和另个线程相联和在当前线程线程组及子组中列举所有激活线程我也会讨论线程调试辅助及用户线程和监督线程对比
我将在以后文章中介绍线程思路方法余下部份Sun不赞成思路方法除外
警告
Sun有些不赞成线程思路方法种类比如suspend和resume它们能锁住你或破坏对象所以你不必在你代码中它们考虑到针对这些思路方法工作区SDK文件在这篇文章中我没有包含这些思路方法
构造线程
Thread有 8个构造器最简单是:
·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参数
最后 4个构造器的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思路方法例如要开始第 2个线程应用开始线程—它执行()—start()作为响应JVM和平台起工作线程操作代码确保线程正确地化并Thread或其子类对象run思路方法
旦start完成多重线程便运行我们趋向于在种线性方式中思维我们常发现当两个或更多线程正运行时理解并发(同时)行为是困难因此你应该看看显示和时间对比个线程正在哪里执行(它位置)图表下图就是这样个图表
和时间对比个开始线程和个新建线程执行位置行为
图表显示了几个重要时间段:
·开始线程化
·线程开始执行()瞬间
·线程开始执行start()瞬间
·start创建个新线程并返回()瞬间
·新线程化
·新线程开始执行run()瞬间
·每个线程结束区别瞬间
注意新线程化它对run()执行和它结束都和开始线程执行同时发生
警告
个线程start后在run()思路方法退出前并发那思路方法将导致start掷出个java.lang.IllegalThreadStateException对象
怎样使用名称
在个调试会话期间使用用户友好方式从另个线程区别其中个线程证明是有帮助要区分其中个线程Java给个线程取个名称Thread缺省名称是个短线连和个零开始数号你可以接受Java缺省线程名称或选择使用你自己为了能够自定义名称Thread提供带有name参数和个Name(String name)思路方法构造器Thread也提供个getName思路方法返回当前名称表2显示了怎样通过Thread(String name)创建个自定义名称和通过在run思路方法中getName检索当前名称:
表2.NameThatThread.java
// NameThatThread.java
NameThatThread
{
public void (String args)
{
MyThread mt;
(args.length 0)
mt = MyThread ;
mt = MyThread (args [0]);
mt.start ;
}
}
MyThread extends Thread
{
MyThread
{
//编译器创建等价于super字节代码
}
MyThread (String name)
{
super (name); //将名称传递给Thread超类
}
public void run
{
.out.prln ("My name is: " + getName );
}
}
你能够在命令行向MyThread传递个可选name参数例如java NameThatThread X 建立X作为线程名称如果你指定个名称失败你将看到下面输出:
My name is: Thread-1
如果你喜欢你能够在MyThread(String name)构造器中将super(name)改变成Name(String name)——作为Name(name)后种思路方法达到同样建立线程名称目——作为super(name)我作为练习保留给你们
注意
Java主要将名称指派给运行 思路方法线程开始线程你特别要看看当开始线程掷出个例外对象时在线程“”例外显示JVM缺省例外处理打印消息
休眠或停止休眠
在这栏后面我将向你介绍动画——在个表面上重复画图形这稍微区别于完成个运动画面要完成动画个线程必须在它显示两个连续画面时中止Thread静态sleep(long millis)思路方法强迫个线程中止millis毫秒另个线程可能中断正在休眠线程如果这种事发生正在休眠线程将醒来并从sleep(long millis)思路方法掷出个InterruptedException对象结果sleep(long millis)代码必须在个try代码块中出现——或代码思路方法必须在自己throws子句中包括InterruptedException
为了示范sleep(long millis)我写了个CalcPI1应用这个应用开始了个新线程便于用个数学运算法则计算数学常量pi值当新线程计算时开始线程通过sleep(long millis)中止10毫秒在开始线程醒后它将打印pi值其中新线程存贮在变量pi中表3给出了CalcPI1源代码:
表3. CalcPI1.java
// CalcPI1.java
CalcPI1
{
public void (String args)
{
MyThread mt = MyThread ;
mt.start ;
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
.out.prln ("pi = " + mt.pi);
}
}
MyThread extends Thread
{
boolean negative = true;
double pi; //缺省化为0.0
public void run
{
for ( i = 3; i < 100000; i 2)
{
(negative)
pi -= (1.0 / i);
pi (1.0 / i);
negative = !negative;
}
pi 1.0;
pi *= 4.0;
.out.prln ("Finished calculating PI");
}
}
如果你运行这个你将看到输出如下(但也可能不样):
pi = -0.2146197014017295
完成计算PI
为什么输出不正确呢?毕竟pi值应近似等于3.14159回答是:开始线程醒得太快了在新线程刚开始计算pi时开始线程就醒过来读取pi当前值并打印其值我们可以通过将10毫秒延迟增加为更长值来进行补偿这更长值(不幸是它是依赖于平台)将给新线程个机会在开始线程醒过来的前完成计算(后面你将学到种不依赖平台技术它将防止开始线程醒来直到新线程完成)
注意
线程同时提供个sleep(long millis, nanos)思路方法它将线程休眠millis 毫秒和nanos 纳秒多数基于JVM平台都不支持纳秒级分解度JVM 线程处理代码将纳秒数字 4舍 5入成毫秒数字近似值如果个平台不支持毫秒级分解度JVM 线程处理代码将毫秒数字 4舍 5入成平台支持最小级分解度近似倍数
它是死还是活?
当个Threadstart思路方法时在个新线程run的前有个时间段(为了化)run返回后在JVM清除线程的前有段时间通过JVM认为线程立即激活优先于线程run在线程执行run期间和run返回后在这时间间隔期间ThreadisAlive思路方法返回个布尔真值否则思路方法返回个假值
isAlive在个线程需要在第个线程能够检查其它线程结果的前等待另个线程完成其run思路方法情形下证明是有帮助实质上那些需要等待线程输入个while循环当isAlive为其它线程返回真值时等待线程sleep(long millis) (或 sleep(long millis, nanos))周期性地休眠 (避免浪费更多CPU循环)旦isAlive返回假值等待线程便检查其它线程结果
你将在哪里使用这样技术呢?对于起动器个CalcPI1修改版本如何样在打印pi值前开始线程在哪里等待新线程完成?表4CalcPI2源代码示范了这技术:
表4. CalcPI2.java
// CalcPI2.java
CalcPI2
{
public void (String args)
{
MyThread mt = MyThread ;
mt.start ;
while (mt.isAlive )
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
.out.prln ("pi = " + mt.pi);
}
}
MyThread extends Thread
{
boolean negative = true;
double pi; //缺省化成0.0
public void run
{
for ( i = 3; i < 100000; i 2)
{
(negative)
pi -= (1.0 / i);
pi (1.0 / i);
negative = !negative;
}
pi 1.0;
pi *= 4.0;
.out.prln ("Finished calculating PI");
}
}
CalcPI2开始线程在10毫秒时间间隔休眠直到mt.isAlive 返回假值当那些发生时开始线程从它while循环中退出并打印pi内容如果你运行这个你将看到如下输出(但不定样):
完成计算PI
pi = 3.1415726535897894
这不现在看上去更精确了?
注意
个线程可能对它自己isAlive 思路方法然而这毫无意义isAlive将直返回真值
合力
while循环/isAlive思路方法/sleep思路方法技术证明是有用Sun将其打包进 3个思路方法组成个组合里:joinjoin(long millis)和join(long millis, nanos)当当前线程想等待其它线程结束时经由另个线程线程对象引用join相反当它想其中任意线程等待其它线程结束或等待直到millis毫秒和nanos纳秒组合通过时当前线程join(long millis)或join(long millis, nanos)(作为sleep思路方法JVM 线程处理代码将对join(long millis)和join(long millis, nanos)思路方法参数值 4舍 5入)表5CalcPI3源代码示范了个对join:
表5. CalcPI3.java
// CalcPI3.java
CalcPI3
{
public void (String args)
{
MyThread mt = MyThread ;
mt.start ;
try
{
mt.join ;
}
catch (InterruptedException e)
{
}
.out.prln ("pi = " + mt.pi);
}
}
MyThread extends Thread
{
boolean negative = true;
double pi; //缺省化成0.0
public void run
{
for ( i = 3; i < 100000; i 2)
{
(negative)
pi -= (1.0 / i);
pi (1.0 / i);
negative = !negative;
}
pi 1.0;
pi *= 4.0;
.out.prln ("Finished calculating PI");
}
}
CalcPI3开始线程等待和MyThread对象有关被mt引用线程结束接着开始线程打印pi值其值和CalcPI2输出样
警告
不要试图将当前线程和其自身连接这样当前线程将要永远等待
怎样使用名称
在个调试会话期间使用用户友好方式从另个线程区别其中个线程证明是有帮助要区分其中个线程Java给个线程取个名称Thread缺省名称是个短线连和个零开始数号你可以接受Java缺省线程名称或选择使用你自己为了能够自定义名称Thread提供带有name参数和个Name(String name)思路方法构造器Thread也提供个getName思路方法返回当前名称表2显示了怎样通过Thread(String name)创建个自定义名称和通过在run思路方法中getName检索当前名称:
表2.NameThatThread.java
// NameThatThread.java
NameThatThread
{
public void (String args)
{
MyThread mt;
(args.length 0)
mt = MyThread ;
mt = MyThread (args [0]);
mt.start ;
}
}
MyThread extends Thread
{
MyThread
{
//编译器创建等价于super字节代码
}
MyThread (String name)
{
super (name); //将名称传递给Thread超类
}
public void run
{
.out.prln ("My name is: " + getName );
}
}
你能够在命令行向MyThread传递个可选name参数例如java NameThatThread X 建立X作为线程名称如果你指定个名称失败你将看到下面输出:
My name is: Thread-1
如果你喜欢你能够在MyThread(String name)构造器中将super(name)改变成Name(String name)——作为Name(name)后种思路方法达到同样建立线程名称目——作为super(name)我作为练习保留给你们
注意
Java主要将名称指派给运行 思路方法线程开始线程你特别要看看当开始线程掷出个例外对象时在线程“”例外显示JVM缺省例外处理打印消息
休眠或停止休眠
在这栏后面我将向你介绍动画——在个表面上重复画图形这稍微区别于完成个运动画面要完成动画个线程必须在它显示两个连续画面时中止Thread静态sleep(long millis)思路方法强迫个线程中止millis毫秒另个线程可能中断正在休眠线程如果这种事发生正在休眠线程将醒来并从sleep(long millis)思路方法掷出个InterruptedException对象结果sleep(long millis)代码必须在个try代码块中出现——或代码思路方法必须在自己throws子句中包括InterruptedException
为了示范sleep(long millis)我写了个CalcPI1应用这个应用开始了个新线程便于用个数学运算法则计算数学常量pi值当新线程计算时开始线程通过sleep(long millis)中止10毫秒在开始线程醒后它将打印pi值其中新线程存贮在变量pi中表3给出了CalcPI1源代码:
表3. CalcPI1.java
// CalcPI1.java
CalcPI1
{
public void (String args)
{
MyThread mt = MyThread ;
mt.start ;
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
.out.prln ("pi = " + mt.pi);
}
}
MyThread extends Thread
{
boolean negative = true;
double pi; //缺省化为0.0
public void run
{
for ( i = 3; i < 100000; i 2)
{
(negative)
pi -= (1.0 / i);
pi (1.0 / i);
negative = !negative;
}
pi 1.0;
pi *= 4.0;
.out.prln ("Finished calculating PI");
}
}
如果你运行这个你将看到输出如下(但也可能不样):
pi = -0.2146197014017295
完成计算PI
为什么输出不正确呢?毕竟pi值应近似等于3.14159回答是:开始线程醒得太快了在新线程刚开始计算pi时开始线程就醒过来读取pi当前值并打印其值我们可以通过将10毫秒延迟增加为更长值来进行补偿这更长值(不幸是它是依赖于平台)将给新线程个机会在开始线程醒过来的前完成计算(后面你将学到种不依赖平台技术它将防止开始线程醒来直到新线程完成)
注意
线程同时提供个sleep(long millis, nanos)思路方法它将线程休眠millis 毫秒和nanos 纳秒多数基于JVM平台都不支持纳秒级分解度JVM 线程处理代码将纳秒数字 4舍 5入成毫秒数字近似值如果个平台不支持毫秒级分解度JVM 线程处理代码将毫秒数字 4舍 5入成平台支持最小级分解度近似倍数
它是死还是活?
当个Threadstart思路方法时在个新线程run的前有个时间段(为了化)run返回后在JVM清除线程的前有段时间通过JVM认为线程立即激活优先于线程run在线程执行run期间和run返回后在这时间间隔期间ThreadisAlive思路方法返回个布尔真值否则思路方法返回个假值
isAlive在个线程需要在第个线程能够检查其它线程结果的前等待另个线程完成其run思路方法情形下证明是有帮助实质上那些需要等待线程输入个while循环当isAlive为其它线程返回真值时等待线程sleep(long millis) (或 sleep(long millis, nanos))周期性地休眠 (避免浪费更多CPU循环)旦isAlive返回假值等待线程便检查其它线程结果
你将在哪里使用这样技术呢?对于起动器个CalcPI1修改版本如何样在打印pi值前开始线程在哪里等待新线程完成?表4CalcPI2源代码示范了这技术:
表4. CalcPI2.java
// CalcPI2.java
CalcPI2
{
public void (String args)
{
MyThread mt = MyThread ;
mt.start ;
while (mt.isAlive )
try
{
Thread.sleep (10); //休眠10毫秒
}
catch (InterruptedException e)
{
}
.out.prln ("pi = " + mt.pi);
}
}
MyThread extends Thread
{
boolean negative = true;
double pi; //缺省化成0.0
public void run
{
for ( i = 3; i < 100000; i 2)
{
(negative)
pi -= (1.0 / i);
pi (1.0 / i);
negative = !negative;
}
pi 1.0;
pi *= 4.0;
.out.prln ("Finished calculating PI");
}
}
CalcPI2开始线程在10毫秒时间间隔休眠直到mt.isAlive 返回假值当那些发生时开始线程从它while循环中退出并打印pi内容如果你运行这个你将看到如下输出(但不定样):
完成计算PI
pi = 3.1415726535897894
这不现在看上去更精确了?
注意
个线程可能对它自己isAlive 思路方法然而这毫无意义isAlive将直返回真值
合力
while循环/isAlive思路方法/sleep思路方法技术证明是有用Sun将其打包进 3个思路方法组成个组合里:joinjoin(long millis)和join(long millis, nanos)当当前线程想等待其它线程结束时经由另个线程线程对象引用join相反当它想其中任意线程等待其它线程结束或等待直到millis毫秒和nanos纳秒组合通过时当前线程join(long millis)或join(long millis, nanos)(作为sleep思路方法JVM 线程处理代码将对join(long millis)和join(long millis, nanos)思路方法参数值 4舍 5入)表5CalcPI3源代码示范了个对join:
表5. CalcPI3.java
// CalcPI3.java
CalcPI3
{
public void (String args)
{
MyThread mt = MyThread ;
mt.start ;
try
{
mt.join ;
}
catch (InterruptedException e)
{
}
.out.prln ("pi = " + mt.pi);
}
}
MyThread extends Thread
{
boolean negative = true;
double pi; //缺省化成0.0
public void run
{
for ( i = 3; i < 100000; i 2)
{
(negative)
pi -= (1.0 / i);
pi (1.0 / i);
negative = !negative;
}
pi 1.0;
pi *= 4.0;
.out.prln ("Finished calculating PI");
}
}
CalcPI3开始线程等待和MyThread对象有关被mt引用线程结束接着开始线程打印pi值其值和CalcPI2输出样
警告
不要试图将当前线程和其自身连接这样当前线程将要永远等待
查询活跃线程
在有些情形下你可能想了解在你中哪些线程是激活Thread支持对思路方法帮助你完成这个任务: activeCount和 enumerate(Thread thd.gif' />)但那些思路方法只工作在当前线程线程组中换句话说那些思路方法只识别属于当前线程同线程组活跃线程 (我将在以后系列文章中讨论线程组——种组织机制)
静态activeCount思路方法返回在当前线程线程组中正在活跃运行线程数量个利用这个思路方法整数返回值设定个Thread引用大小检索那些引用必须静态enumerate(Thread thd.gif' />)思路方法这个思路方法整数返回值确定Thread引用存贮在中enumerate(Thread thd.gif' />)总数要看这些思路方法如何起工作请查看表6:
表6. Census.java
// Census.java
Census
{
public void (String args)
{
Thread threads = Thread [Thread.activeCount ];
n = Thread.enumerate (threads);
for ( i = 0; i < n; i)
.out.prln (threads [i].toString );
}
}
在运行时这个会产生如下输出:
Thread[,5,]
输出显示个线程开始线程正在运行左边表示线程名称5显示线程优先权右边表示线程线程组你也许很失望不能在输出中看到任何系统线程比如垃圾收集器线程那种限制由Threadenumerate(Thread thd.gif' />) 思路方法产生它仅询问当前线程线程组活跃线程然而 ThreadGroup类包含多种enumerate思路方法允许你捕获对所有活跃线程引用而不管线程组在稍后系列中探讨ThreadGroup时我将向你显示如何列举所有引用
警告
当重申个时不要依靠activeCount返回值如果你这样做了你将冒掷出个NullPoerException对象风险为什么呢?在activeCount和enumerate(Thread thd.gif' />)的间个或更多线程可能结束结果 enumerate(Thread thd.gif' />)能够复制少数线程引用进它因此仅考虑将activeCount返回值作为可能大小最大值同样考虑将enumerate(Thread thd.gif' />)返回值作为在个对那种思路方法时活跃线程数目
反臭虫
如果你出现故障并且你怀疑问题出在线程通过ThreaddumpStack和toString思路方法你能够了解到线程更多细节静态dumpStack思路方法提供个 Exception ("Stack trace").prStackTrace 封装打印个追踪当前线程堆栈toString依据下面格式返回个描述线程名称、优先权和线程组串: Thread[thread-name,priority,thread-group]. (在稍后系列中你将学到更多有关优先权知识)
窍门技巧
在些地方这篇文章提到了当前线程概念如果你需要访问描述当前线程Thread对象则Thread静态currentThread思路方法例:Thread current = Thread.currentThread
等级系统
不是所有线程都被平等创建它们被分成两类:用户和监督个用户线程执行着对于用户十分重要工作工作必须在结束前完成相反个监督线程执行着后勤事务(比如垃圾收集)和其它可能不会对应用主要工作作出贡献但对于应用继续它主要工作却非常必要后台任务和用户线程不样监督线程不需要在应用结束前完成当个应用开始线程(它是个用户线程)结束时JVM检查是否还有其它用户线程正在运行如果有JVM就会阻止应用结束否则JVM就会结束应用而不管监督线程是否正在运行
当个线程个线程对象start思路方法时新已经开始线程就是个用户线程那是缺省要建立个线程作为监督线程必须在start前Thread个带布尔真值参数Daemon(boolean isDaemon)思路方法稍后你可以通过ThreadisDaemon思路方法检查个线程是否是监督线程如果是监督线程那个思路方法返回个布尔真值
为了让你试试用户和监督线程我写了个UserDaemonThreadDemo:
表7. UserDaemonThreadDemo.java
// UserDaemonThreadDemo.java
UserDaemonThreadDemo
{
public void (String args)
{
(args.length 0)
MyThread .start ;
{
MyThread mt = MyThread ;
mt.Daemon (true);
mt.start ;
}
try
{
Thread.sleep (100);
}
catch (InterruptedException e)
{
}
}
}
MyThread extends Thread
{
public void run
{
.out.prln ("Daemon is " + isDaemon );
while (true);
}
}
编译了代码后通过Java2 SDKjava命令运行UserDaemonThreadDemo如果你没有使用命令行参数运行例如java UserDaemonThreadDemo MyThread .start 执行这段代码片断开始个在进入个无限循环前打印Daemon is false用户线程(你必须按Ctrl-C或个等价于结束个无限循环组合按键)新线程是个用户线程应用在开始线程结束后仍保持运行然而如果你指定了至少个命令行参数例如java UserDaemonThreadDemo xmt.Daemon (true)执行并且新线程将是个监督线程结果旦开始线程从100毫秒休眠中醒来并结束新监督线程也将结束
警告
如果线程开始执行后Daemon(boolean isDaemon)思路方法Daemon(boolean isDaemon)思路方法将掷出个IllegalThreadStateException对象
Runnable
学习前面部份例子后你可能认为引入多线程进入个类总是要求你去扩展Thread并将你子类重载Thread's run思路方法然而那并不总是种选择Java对继承强制执行禁止个类扩展两个或更多个超类结果如果个类扩展了个无线程类那个类就不能扩展Thread. 假使限制怎样才可能将多线程引入个已经扩展了其它类类?幸运是 Java设计者已经意识到不可能创建Thread子类情形总会发生这导致产生java.lang.Runnable接口和带Runnable参数Thread构造器如Thread(Runnable target)
Runnable接口声明了个单独思路方法署名:void run这个署名和Threadrun思路方法署名样并作为线程执行入口服务Runnable是个接口任何类都能通过将个implements子句包含进类头和提供个适当run思路方法实现接口在执行时间代码能从那个类创建个对象或runnable并将runnable引用传递给个适当Thread构造器构造器和Thread对象起存贮这个引用并确保个新线程在Thread对象start思路方法后runnablerun思路方法示范如表8:
表8.RunnableDemo.java
// RunnableDemo.java
RunnableDemo
{
public void (String args)
{
Rectangle r = Rectangle (5, 6);
r.draw ;
//用随机选择宽度和高度画区别长方形
Rectangle ;
}
}
abstract Shape
{
abstract void draw ;
}
Rectangle extends Shape implements Runnable
{
private w, h;
Rectangle
{
//创建个绑定这个runnable新Thread对象并开始个将这个runnable
//run思路方法线程
Thread (this).start ;
}
Rectangle ( w, h)
{
(w < 2)
throw IllegalArgumentException ("w value " + w + " < 2");
(h < 2)
throw IllegalArgumentException ("h value " + h + " < 2");
this.w = w;
this.h = h;
}
void draw
{
for ( c = 0; c < w; c)
.out.pr ('*');
.out.pr ('\n');
for ( r = 0; r < h - 2; r)
{
.out.pr ('*');
for ( c = 0; c < w - 2; c)
.out.pr (' ');
.out.pr ('*');
.out.pr ('\n');
}
for ( c = 0; c < w; c)
.out.pr ('*');
.out.pr ('\n');
}
public void run
{
for ( i = 0; i < 20; i)
{
w = rnd (30);
(w < 2)
w 2;
h = rnd (10);
(h < 2)
h 2;
draw ;
}
}
rnd ( limit)
{
//在0<=x<界限范围内返回个随机数字x
() (Math.random * limit);
}
}
RunnableDemo由类RunnableDemoShape和Rectangle组成类RunnableDemo通过创建个Rectangle对象驱动应用—通过对象draw思路方法—和通过创建第 2个什么都不做Rectangle类相反Shape和Rectangle组成了个基于shape层次类Shape是抽象它提供个抽象draw思路方法各种shape类比如Rectangle扩展Shape和描述它们如何画它们自己重载draw以后我可能决定引入些另外shape类创建个Shape通过Shapedraw思路方法要求每个Shape元素画它自己
RunnableDemo 作为个不带多线程简单产生后面我决定引入多线程到Rectangle这样我能够用各种宽度和高度画种种矩形Rectangle扩展Shape (为了以后多态性原因)我没有其它选择只有让Rectangle实现Runnable同样在Rectangle构造器内我不得不将个Rectangle runnable绑定到个新Thread对象并Threadstart思路方法开始个新线程Rectanglerun思路方法画矩形
包括在这篇文章中RunnableDemo新输出太长了我建议你自己编译并运行
窍门技巧
当你面对个类不是能扩展Thread就是能实现Runnable情形时你将选择哪种思路方法?如果这个类已经扩展了其它类你必须实现Runnable然而如果这个类没有扩展其它类考虑下类名称名称将暗示这个类对象不是积极就是消极例如名称Ticker暗示它对象是积极因此Ticker类将扩展Thread并且Ticker对象将被作为专门Thread对象相反Rectangle暗示消极对象—Rectangle对象对于它们自己什么也不做因此Rectangle类将实现Runnable,并且Rectangle 对象将使用Thread对象(为了测试或其它意图)代替成为专门Thread对象
回顾
用户期望达到优异性能种办法是用线程完成那些任务个线程是条代码独立执行通道线程有益于基于GUI它们允许那些当执行其它任务时仍对用户保持响应另外带线程比它们没带线程副本完成快这对于运行在多处理器机器上情形尤其明显在这里每个线程有它自己处理器Thread和Thread子类对象描述了线程并和那些实体相关对于那些不能扩展Thread类你必须创建个runnable以利用多线程优势
0
相关文章读者评论
发表评论 |