Thread 类是一个具体的类,即不是抽象类,该类封装了线程的行为。要创建一个线程,程序员必须创建一个从 Thread 类导出的新类。程序员必须覆盖 Thread 的 run() 函数来完成有用的工作。用户并不直接调用此函数;而是必须调用 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();
} } |
2.Runnable 接口
此接口只有一个函数,run() ,此函数必须由实现了此接口的类实现。但是,就运行这个类而论,其语义与前一个示例稍有不同。我们可以用 runnable 接口改写前一个示例。(不同的部分用黑体表示。)
创建两个新线程而不强加类层次
import java.util.*; class TimePrinter implements Runnable { 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[]) { Thread t1 = new Thread (new TimePrinter(1000, "Fast Guy")); t1.start(); Thread t2 = new Thread (new TimePrinter(3000, "Slow Guy")); t2.start();
} }
|
请注意,当使用 runnable 接口时,您不能直接创建所需类的对象并运行它;必须从 Thread 类的一个实例内部运行它。许多程序员更喜欢 runnable 接口,因为从 Thread 类继承会强加类层次
3 synchronized 关键字
4 线程组
线程是被个别创建的,但可以将它们归类到 线程组 中,以便于调试和监视。只能在创建线程的同时将它与一个线程组相关联。在使用大量线程的程序中,使用线程组组织线程可能很有帮助。可以将它们看作是计算机上的目录和文件结
5 线程间发信
当线程在继续执行前需要等待一个条件时,仅有 synchronized 关键字是不够的。虽然 synchronized 关键字阻止并发更新一个对象,但它没有实现 线程间发信 。 Object 类为此提供了三个函数: wait() 、 notify() 和 notifyAll() 。
6 将 CPU 让给另一个线程
当线程放弃某个稀有的资源(如数据库连接或网络端口)时,它可能调用 yield() 函数临时降低自己的优先级,以便某个其他线程能够运行
7 守护线程
有两类线程:用户线程和守护线程。 用户线程 是那些完成有用工作的线程。 守护线程 是那些仅提供辅助功能的线程。 Thread 类提供了 setDaemon() 函数。 Java 程序将运行到所有用户线程终止,然后它将破坏所有的守护线程。在 Java 虚拟机 (JVM) 中,即使在 main 结束以后,如果另一个用户线程仍在运行,则程序仍然可以继续运行。
不提倡使用的方法是为支持向后兼容性而保留的那些方法,它们在以后的版本中可能出现,也可能不出现。Java 多线程支持在版本 1.1 和版本 1.2 中做了重大修订,stop() 、suspend() 和 resume() 函数已不提倡使用。这些函数在 JVM 中可能引入微妙的错误。虽然函数名可能听起来很诱人,但请抵制诱惑不要使用它们
在线程化的程序中,可能发生的某些常见而讨厌的情况是死锁、活锁、内存损坏和资源耗尽。
死锁
死锁可能是多线程程序最常见的问题。当一个线程需要一个资源而另一个线程持有该资源的锁时,就会发生死锁。这种情况通常很难检测。但是,解决方案却相当好:在所有的线程中按相同的次序获取所有资源锁。例如,如果有四个资源 ―A 、B 、C 和 D ― 并且一个线程可能要获取四个资源中任何一个资源的锁,则请确保在获取对 B 的锁之前首先获取对 A 的锁,依此类推。如果“ 线程 1” 希望获取对 B 和 C 的锁,而“ 线程 2” 获取了 A 、C 和 D 的锁,则这一技术可能导致阻塞,但它永远不会在这四个锁上造成死锁。
活锁
当一个线程忙于接受新任务以致它永远没有机会完成任何任务时,就会发生活锁。这个线程最终将超出缓冲区并导致程序崩溃。试想一个秘书需要录入一封信,但她一直在忙于接电话,所以这封信永远不会被录入。
内存损坏
如果明智地使用 synchronized 关键字,则完全可以避免内存错误这种气死人的问题。
资源耗尽
某些系统资源是有限的,如文件描述符。多线程程序可能耗尽资源,因为每个线程都可能希望有一个这样的资源。如果线程数相当大,或者某个资源的侯选线程数远远超过了可用的资源数,则最好使用 资源池 。一个最好的示例是数据库连接池。只要线程需要使用一个数据库连接,它就从池中取出一个,使用以后再将它返回池中。资源池也称为 资源库 。
10. 首先来介绍一个概念,活动对象(Active Object )。所谓活动对象是相对于被动对象(passive object )而言的,被动对象的方法的调用和执行都是在同一个线程中的 ,被动对象方法的调用是同步的、阻塞的,一般的对象都属于被动对象;主动对象的方 法的调用和执行是分离的,主动对象有自己独立的执行线程,主动对象的方法的调用是由其他线程发起的,但是方法是在自己的线程中执行的,主动对象方法的调用 是异步的,非阻塞的。
11. 简单线程框架
//ActiveObject 的定义
public class ActiveObject extends Thread {
private ActiveQueue _queue ;
public ActiveObject() {
_queue = new ActiveQueue();
start();
}
public void enqueue (MethodRequest mr) {
_queue .enqueue(mr);
}
public void run() {
while ( true ) {
MethodRequest mr = _queue .dequeue();
mr.call();
}
}
}
//ActiveQueue 的定义
public class ActiveQueue {
private Stack _queue ;
private final static int QUEUE_SIZE = 4;
public ActiveQueue() {
_queue = new Stack();
}
public synchronized void enqueue (MethodRequest mr) {
while ( _queue .size() > QUEUE_SIZE ) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
_queue .push(mr);
notifyAll();
System. out .println( "Leave Queue" );
}
public synchronized MethodRequest dequeue() {
MethodRequest mr;
while ( _queue .empty()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mr = (MethodRequest) _queue .pop();
notifyAll();
return mr;
}
}
public interface MethodRequest {
public void call();
}
12 线程池的技术背景
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java 中更是如此,虚拟机将试 图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创 建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些" 池化资源" 技术产生的原因
一般一个简单线程池至少包含下列组成部分。
- 线程池管理器(ThreadPoolManager ): 用于创建并管理线程池
- 工作线程(WorkThread ): 线程池中线程
- 任务接口(Task ): 每个任务必须实现的接口,以供工作线程调度任务的执行。
- 任务队列: 用于存放没有处理的任务。提供一种缓冲机制。