线程笔记(3)

线程通信

传统的线程通信(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ThreadGroupTest {
public static void main(String[] args) {
System.out.println("主线程组的名字:" + Thread.currentThread().getThreadGroup().getName());

System.out.println("主线程组是否是后台线程组:" + Thread.currentThread().getThreadGroup().isDaemon());

//创建一个没有显示指定线程组的线程
MyThread thread1 = new MyThread("线程1");
System.out.println(thread1.getName() + "所属的线程组:" + thread1.getThreadGroup().getName());
thread1.start();

//创建一个新的线程组
ThreadGroup tg1 = new ThreadGroup("线程组1");
//将该线程组设置为后台线程组
tg1.setDaemon(true);
System.out.println(tg1.getName() + "是否是后台线程组:" + tg1.isDaemon());

//创建一个指定了线程组的线程
MyThread thread2 = new MyThread(tg1, "线程2");
System.out.println(thread2.getName() + "所属的线程组:" + thread2.getThreadGroup().getName());
System.out.println(thread2.getName() + "是否是后台线程:" + thread2.isDaemon());
thread2.start();
}
}

输出结果

1
2
3
4
5
6
7
8
主线程组的名字:main
主线程组是否是后台线程组:false
线程1所属的线程组:main
线程组1是否是后台线程组:true
线程2所属的线程组:线程组1
线程2是否是后台线程:false
执行线程:线程1
执行线程:线程2

未处理异常

  • 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
    15
    public 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);
    }
    }
    }

可以看出,线程组处理异常的默认流程如下:

  1. 如果该线程组有父线程组,则调用父线程组的uncaughtException方法
  2. 如果该线程类有默认的异常处理器(由DefaultUncaughtExceptionHandler方法设置),那么就调用该异常处理器来处理异常
  3. 如果该异常对象是ThreadDeath对象,则不做任何处理;否则,将异常跟踪栈的信息打印到System.err错误输出流,并结束该线程。

简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ExHandler {
public static void main(String[] args) {
//设置主线程的异常处理器
Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());
//异常语句
int a = 5 / 0;

System.out.println("程序正常结束");
}
}

//定义自己的异常处理器
class MyExHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t.getName() + "线程出现了异常:" + e);
}
}

输出结果

1
main线程出现了异常:java.lang.ArithmeticException: / by zero

可以看出,虽然异常处理器对未捕获的异常进行了处理,但程序依然不会正常结束。这说明异常处理器与通过catch捕获异常是不一样的————当使用catch捕获异常时,异常不会向上传播给上一级调用者;但使用异常处理器对异常进行处理之后,异常依然会传播给上一级调用者。

如果加上try…catch块会怎样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
public 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
2
catch处理,异常是:java.lang.ArithmeticException: / by zero
程序正常结束

可以看出,异常是由catch块处理的,不会由异常处理器处理;而且异常不会向上传播给上一级调用者,程序可以正常结束。

参考

-------------    本文到此结束  感谢您的阅读    -------------
0%