📄 java 程序中的多线程——看一看开发和使用线程是多么容易.htm
字号:
TimePrinter tp2 = new TimePrinter(3000, "Slow Guy");
tp2.start();
}
}
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>在本例中,我们可以看到一个简单的程序,它按两个不同的时间间隔(1 秒和 3
秒)在屏幕上显示当前时间。这是通过创建两个新线程来完成的,包括 <TT>main()</TT>
共三个线程。但是,因为有时要作为线程运行的类可能已经是某个类层次的一部分,所以就不能再按这种机制创建线程。虽然在同一个类中可以实现任意数量的接口,但
Java 编程语言只允许一个类有一个父类。同时,某些程序员避免从 Thread 类导出,因为它强加了类层次。对于这种情况,就要
<I>runnable 接口</I>。</P>
<P><B>Runnable
接口</B><BR>此接口只有一个函数,<TT>run()</TT>,此函数必须由实现了此接口的类实现。但是,就运行这个类而论,其语义与前一个示例稍有不同。我们可以用
runnable 接口改写前一个示例。(不同的部分用黑体表示。) <!-- and show the differences in red. --></P>
<P><I>创建两个新线程而不强加类层次</I></P>
<TABLE class=code-sample cellPadding=0 width="98%" border=0>
<TBODY>
<TR>
<TD><PRE>import java.util.*;
class TimePrinter <B>implements Runnable</B> {
int pauseTime;
String name;
public TimePrinter(int x, String n) {
pauseTime = x;
name = n;
}
public void run() {
while(true) {
try {
System.out.println(name + ":" + new
Date(System.currentTimeMillis()));
Thread.sleep(pauseTime);
} catch(Exception e) {
System.out.println(e);
}
}
}
static public void main(String args[]) {
<B>Thread t1 = new Thread</B>(new TimePrinter(1000, "Fast Guy"));
t1.start();
<B>Thread t2 = new Thread</B>(new TimePrinter(3000, "Slow Guy"));
t2.start();
}
}
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>请注意,当使用 runnable 接口时,您不能直接创建所需类的对象并运行它;必须从 Thread
类的一个实例内部运行它。许多程序员更喜欢 runnable 接口,因为从 Thread 类继承会强加类层次。</P>
<P><B>synchronized
关键字</B><BR>到目前为止,我们看到的示例都只是以非常简单的方式来利用线程。只有最小的数据流,而且不会出现两个线程访问同一个对象的情况。但是,在大多数有用的程序中,线程之间通常有信息流。试考虑一个金融应用程序,它有一个
Account 对象,如下例中所示:</P>
<P><I>一个银行中的多项活动</I></P>
<TABLE class=code-sample cellPadding=0 width="98%" border=0>
<TBODY>
<TR>
<TD><PRE>public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public void deposit(float amt) {
amount += amt;
}
public void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>在此代码样例中潜伏着一个错误。如果此类用于单线程应用程序,不会有任何问题。但是,在多线程应用程序的情况中,不同的线程就有可能同时访问同一个
Account 对象,比如说一个联合帐户的所有者在不同的 ATM
上同时进行访问。在这种情况下,存入和支出就可能以这样的方式发生:一个事务被另一个事务覆盖。这种情况将是灾难性的。但是,Java
编程语言提供了一种简单的机制来防止发生这种覆盖。每个对象在运行时都有一个关联的锁。这个锁可通过为方法添加关键字
<TT>synchronized</TT> 来获得。这样,修订过的 Account
对象(如下所示)将不会遭受像数据损坏这样的错误:</P>
<P><I>对一个银行中的多项活动进行同步处理</I></P>
<TABLE class=code-sample cellPadding=0 width="98%" border=0>
<TBODY>
<TR>
<TD><PRE>public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public <B>synchronized</B> void deposit(float amt) {
amount += amt;
}
public <B>synchronized</B> void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P><TT>deposit()</TT> 和 <TT>withdraw()</TT>
函数都需要这个锁来进行操作,所以当一个函数运行时,另一个函数就被阻塞。请注意,<TT> checkBalance()</TT>
未作更改,它严格是一个读函数。因为 <TT>checkBalance()</TT>
未作同步处理,所以任何其他方法都不会阻塞它,它也不会阻塞任何其他方法,不管那些方法是否进行了同步处理。</P><A id=3
name=3></A>
<P><STRONG class=subhead>Java 编程语言中的高级多线程支持</STRONG></P>
<P><B>线程组</B><BR>线程是被个别创建的,但可以将它们归类到<I>线程组</I>中,以便于调试和监视。只能在创建线程的同时将它与一个线程组相关联。在使用大量线程的程序中,使用线程组组织线程可能很有帮助。可以将它们看作是计算机上的目录和文件结构。</P>
<P><B>线程间发信</B><BR>当线程在继续执行前需要等待一个条件时,仅有 <TT>synchronized</TT>
关键字是不够的。虽然 <TT>synchronized</TT>
关键字阻止并发更新一个对象,但它没有实现<I>线程间发信</I>。Object
类为此提供了三个函数:<TT>wait()</TT>、<TT>notify()</TT> 和
<TT>notifyAll()</TT>。以全球气候预测程序为例。这些程序通过将地球分为许多单元,在每个循环中,每个单元的计算都是隔离进行的,直到这些值趋于稳定,然后相邻单元之间就会交换一些数据。所以,从本质上讲,在每个循环中各个线程都必须等待所有线程完成各自的任务以后才能进入下一个循环。这个模型称为<I>
屏蔽同步</I>,下例说明了这个模型:</P>
<P><I>屏蔽同步</I></P>
<TABLE class=code-sample cellPadding=0 width="98%" border=0>
<TBODY>
<TR>
<TD><PRE>public class BSync {
int totalThreads;
int currentThreads;
public BSync(int x) {
totalThreads = x;
currentThreads = 0;
}
public synchronized void waitForAll() {
currentThreads++;
if(currentThreads < totalThreads) {
try {
wait();
} catch (Exception e) {}
}
else {
currentThreads = 0;
notifyAll();
}
}
}
</PRE></TD></TR></TBODY></TABLE><BR><BR>
<P>当对一个线程调用 <TT>wait()</TT> 时,该线程就被有效阻塞,只到另一个线程对同一个对象调用
<TT>notify()</TT> 或 <TT>notifyAll()</TT>
为止。因此,在前一个示例中,不同的线程在完成它们的工作以后将调用 <TT>waitForAll()</TT> 函数,最后一个线程将触发
<TT>notifyAll()</TT> 函数,该函数将释放所有的线程。第三个函数 <TT>notify()</TT>
只通知一个正在等待的线程,当对每次只能由一个线程使用的资源进行访问限制时,这个函数很有用。但是,不可能预知哪个线程会获得这个通知,因为这取决于
Java 虚拟机 (JVM) 调度算法。</P>
<P><B>将 CPU 让给另一个线程</B><BR>当线程放弃某个稀有的资源(如数据库连接或网络端口)时,它可能调用
<TT>yield()</TT> 函数临时降低自己的优先级,以便某个其他线程能够运行。</P>
<P><B>守护线程</B><BR>有两类线程:用户线程和守护线程。<I>用户线程</I>是那些完成有用工作的线程。<I>
守护线程</I>是那些仅提供辅助功能的线程。Thread 类提供了 <TT>setDaemon()</TT> 函数。Java
程序将运行到所有用户线程终止,然后它将破坏所有的守护线程。在 Java 虚拟机 (JVM) 中,即使在 main
结束以后,如果另一个用户线程仍在运行,则程序仍然可以继续运行。</P><A id=4 name=4></A>
<P><STRONG
class=subhead>避免不提倡使用的方法</STRONG><BR>不提倡使用的方法是为支持向后兼容性而保留的那些方法,它们在以后的版本中可能出现,也可能不出现。Java
多线程支持在版本 1.1 和版本 1.2 中做了重大修订,<TT>stop()</TT>、<TT>suspend()</TT> 和
<TT>resume()</TT> 函数已不提倡使用。这些函数在 JVM
中可能引入微妙的错误。虽然函数名可能听起来很诱人,但请抵制诱惑不要使用它们。<!-- The Java gurus suggest that a thread should be allowed to return from <tt>run()</tt> function gracefully instead of using the <tt>stop()</tt> function, which is akin to jumping from a speeding train. --></P><A
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -