线程通信
传统的线程通信(wait、notify、notifyAll)
- wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
- 这三个方法必须有同步监视器对象来调用,这可分为两种情况:
- 对于使用synchronized修饰的同步方法,因为该类的实例this就是同步监视器,所以可以在同步方法中直接使用这三个方法。
- 对于使用synchronized修饰的同步代码块,同步监视器就是synchronized后括号的对象,所以必须使用该对象调用这三个方法。
- wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。调用wait方法的当前线程会释放对该同步监视器的锁定。
- notify():唤醒在此同步监视器上等待的单个线程。如果有多个线程在此同步监视器上等待,则选择唤醒其中一个线程,这种选择是任意性的。
- notifyAll():唤醒在此同步监视器上等待的所有线程。
- 注意:notify/notifyAll方法的执行只是唤醒沉睡的线程,而不会立即释放锁。直到执行完synchronized代码块的代码或者是调用wait方法,才会释放锁。所以在编程中,应尽量在使用了notify/notifyAll方法后立即退出临界区,以唤醒其他线程。
使用Condition控制线程通信
- 如果程序不使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在同步监视器,也就不能使用wait、notify,notifyAll方法来进行线程通信了。这时候就要使用Condition了。
- Condition实例被绑定在一个Lock对象上,其提供了如下三个方法:
- await():类似于wait方法,导致当前线程等待,直到其他线程调用该Condition的signal或signalAll方法来唤醒该线程。
- signal():唤醒在此Lock对象上等待的单个线程。如果多个线程在等待,则会唤醒任意一个线程。
- signalAll():唤醒在此Lock对象上等待的所有线程。
- 注意:signal或signalAll方法也是只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。
使用阻塞队列(BlockingQueue)控制线程通信
- BlockingQueue接口是Queue的子接口,但它的主要作用并不是作为容器,而是作为线程同步的工具。
- BlockingQueue接口的一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
- BlockingQueue提供以下两个方法支持阻塞:
- put(E e):尝试把E元素放入BlockingQueue中,如果该队列元素已满,则阻塞该线程。
- take():尝试从BlockingQueue中取出元素时,如果该队列的元素已空,则阻塞该线程。
线程组
- java使用ThreadGroup来表示线程组,它可以对一批线程进行管理。
- 用户创建的所有线程都属于指定线程组,如果程序没有显示指定线程属于哪个线程组,则该线程默认和创建它的父线程处于同一线程组。
- 一旦某个线程指定了线程组之后,那么该线程将一直属于该线程组,直到该线程死亡,线程运行过程中不能改变它所属的线程组。
简单示例
1 | public class ThreadGroupTest { |
输出结果
1 | 主线程组的名字:main |
未处理异常
- ThreadGroup实现了Thread.UncaughtExceptionHandler接口,这是Thread类的一个内部接口,该接口内只有一个方法:void uncaughtException(Thread t, Throwable e);ThreadGroup通过该方法可以处理该线程组内的任意线程所抛出的未处理异常。
- 从java5开始,java加强了线程的异常处理,如果线程执行过程中抛出了一个未处理异常,JVM在结束该线程之前会自动查找该线程是否有对应的Thread.UncaughtExceptionHandler对象,如果找到该对象,那么将调用该对象的uncaughtException方法来处理该异常。
- 由于ThreadGroup实现了Thread.UncaughtExceptionHandler接口,所以每个线程所属的线程组将会作为该线程的默认异常处理器。
- ThreadGroup中的uncaughtException方法实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
可以看出,线程组处理异常的默认流程如下:
- 如果该线程组有父线程组,则调用父线程组的uncaughtException方法
- 如果该线程类有默认的异常处理器(由DefaultUncaughtExceptionHandler方法设置),那么就调用该异常处理器来处理异常
- 如果该异常对象是ThreadDeath对象,则不做任何处理;否则,将异常跟踪栈的信息打印到System.err错误输出流,并结束该线程。
简单示例
1 | public class ExHandler { |
输出结果1
main线程出现了异常:java.lang.ArithmeticException: / by zero
可以看出,虽然异常处理器对未捕获的异常进行了处理,但程序依然不会正常结束。这说明异常处理器与通过catch捕获异常是不一样的————当使用catch捕获异常时,异常不会向上传播给上一级调用者;但使用异常处理器对异常进行处理之后,异常依然会传播给上一级调用者。
如果加上try…catch块会怎样呢?1
2
3
4
5
6
7
8
9
10
11
12
13public class ExHandler {
public static void main(String[] args) {
//设置主线程的异常处理器
Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());
try {
//异常语句
int a = 5 / 0;
} catch (Exception e) {
System.out.println("catch处理,异常是:" + e);
}
System.out.println("程序正常结束");
}
}
输出结果:1
2catch处理,异常是:java.lang.ArithmeticException: / by zero
程序正常结束
可以看出,异常是由catch块处理的,不会由异常处理器处理;而且异常不会向上传播给上一级调用者,程序可以正常结束。