线程相关知识 创建和启动; 调度和优先级; 状态控制; 线程同步。 java编程语言使多线程如此简单有效,以致于某些程序员说它实际上是自然的。 要记住的一件重要的事情是 main() 函数也是一个线程,并可用来做有用的工作。程序员只有在需要多个线程时才需要创建新的线程。 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口。 在使用Runnable接口时需要建立一个Thread实例。因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。 最好采用实现Runnable接口的方式,创建线程类,因为使用接口之后,该类还可以继承别的类。 1.继承Thread 类 Thread 类是一个具体的类,即不是抽象类,该类封装了线程的行为。 要创建一个线程, 1.创建一个从 Thread 类导出的新类。 2.覆盖(override) Thread 的 run() 函数来完成有用的工作。 3.实例化新建类。 4.用户并不直接调用此run()函数;而是必须调用 Thread 的 start() 函数,该函数再调用 run()。 start()使run()运行之后,线程2开始独立地运行。 下面的代码说明了它的用法: 创建两个新线程 在这里调用了start()后,并不是直接就运行run()方法中的操作,而是进入到就绪(runnable state)状态。等待被分配机器资源,进入运行(running)状态,才运行run()中的操作 class Count extends Thread { public void start() { System.out.println("** Runnable ****"); super.start(); } public void run() { System.out.println("** running *****"); for (int i=0; i <= 5; i++) { System.out.println("Count: " + i); } System.out.println("** Dead ********"); } } class CountTest { public static void main(String[] args) { Count thread = new Count(); System.out.println("** New Thread **"); thread.start(); System.out.println("last line in main()"); } } 结果 C:\Java\Thread>javac CountTest.java C:\Java\Thread>java CountTest ** New Thread ** ** Runnable **** last line in main() ** running ***** Count: 0 Count: 1 Count: 2 Count: 3 Count: 4 Count: 5 ** Dead ******** C:\Java\Thread> 有结果可知调用start()方法,并不是立刻执行run()方法中的内容。而是等待机器给它分配资源,才能开始运行。 例子 import java.util.*; class TimePrinter extends Thread { 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[]) { TimePrinter tp1 = new TimePrinter(1000, "Fast Guy"); tp1.start(); TimePrinter tp2 = new TimePrinter(3000, "Slow Guy"); tp2.start(); } } 在本例中,我们可以看到一个简单的程序,它按两个不同的时间间隔(1 秒和 3 秒)在屏幕上显示当前时间。 这是通过创建两个新线程来完成的,包括 main() 共三个线程。 但是,因为有时要作为线程运行的类可能已经是某个类层次的一部分,所以就不能再按这种机制创建线程。 虽然在同一个类中可以实现任意数量的接口,但 Java 编程语言只允许一个类有一个父类。 同时,某些程序员避免从 Thread 类导出,因为它强加了类层次。对于这种情况,就要 runnable 接口。 2. 实现Runnable 接口 此接口只有一个函数,run(),此函数必须由实现了此接口的类实现。但是,就运行这个类而论,其语义与前一个示例稍有不同。 我们可以用 runnable 接口改写前一个示例。(不同的部分用黑体表示。) 1.实现Runnable接口。 2.实现run()方法。 3.实例化新实现的类。 4.实例化由3.生成的实例作为参数的Tread类。 5.调用start()方法。 创建两个新线程而不强加类层次 import java.util.*; class TimePrinterRunnable implements Runnable { int pauseTime; String name; public TimePrinterRunnable(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[]) { Thread t1 = new Thread(new TimePrinterRunnable(1000, "Fast Guy")); t1.start(); Thread t2 = new Thread(new TimePrinterRunnable(3000, "Slow Guy")); t2.start(); } } 请注意,当使用 runnable 接口时,您不能直接创建所需类的对象并运行它;必须从 Thread 类的一个实例内部运行它。许多程序员更喜欢 runnable 接口,因为从 Thread 类继承会强加类层次。 synchronized 关键字 到目前为止,我们看到的示例都只是以非常简单的方式来利用线程。只有最小的数据流,而且不会出现两个线程访问同一个对象的情况。但是,在大多数有用的程序中,线程之间通常有信息流。试考虑一个金融应用程序,它有一个 Account 对象,如下例中所示: 一个银行中的多项活动 Copy code 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; } } 在此代码样例中潜伏着一个错误。如果此类用于单线程应用程序,不会有任何问题。但是,在多线程应用程序的情况中,不同的线程就有可能同时访问同一个 Account 对象,比如说一个联合帐户的所有者在不同的 ATM 上同时进行访问。在这种情况下,存入和支出就可能以这样的方式发生:一个事务被另一个事务覆盖。这种情况将是灾难性的。但是,Java 编程语言提供了一种简单的机制来防止发生这种覆盖。每个对象在运行时都有一个关联的锁。这个锁可通过为方法添加关键字 synchronized 来获得。这样,修订过的 Account 对象(如下所示)将不会遭受像数据损坏这样的错误: 对一个银行中的多项活动进行同步处理 Copy code public class Account { String holderName; float amount; public Account(String name, float amt) { holderName = name; amount = amt; } public synchronized void deposit(float amt) { amount += amt; } public synchronized void withdraw(float amt) { amount -= amt; } public float checkBalance() { return amount; } } deposit() 和 withdraw() 函数都需要这个锁来进行操作,所以当一个函数运行时,另一个函数就被阻塞。请注意, checkBalance() 未作更改,它严格是一个读函数。因为 checkBalance() 未作同步处理,所以任何其他方法都不会阻塞它,它也不会阻塞任何其他方法,不管那些方法是否进行了同步处理。 问题1.为什么不直接调用run()方法。 答: t1.run()只是方法调用,而不会启动一个新的线程,线程还是原来的那条; 而t1.start()是在当前线程之外再启动一个新的线程。 |
|
来自: 360doc_kim > 《Java》