Java线程概述及API二
1 线程中断interrupt
中断机制的实现是通过一个标记中断状态(interrupt status)实现的。我们通过调用某个线程对象的interrupt方法只是来设置这个标记,而并非直接终止线程的运行。当一个线程调用了Thread的静态方法interrupted判断到自己被中断后,立即会将这个状态清空。但是如果调用Thread的实例方法Isinterrupted判断,则不会清空中断状态。总的来说,线程如何支持中断,这依赖于线程的运行时代码是如何编写的。
当使用interrupt方法中断了一个处于阻塞或者正试图执行阻塞操作的线程时,将会抛出一个InterruptedException异常,更重要的是,中断状态将会被复位。所以下面这种检测中断的方式将失效:
1 Thread t1 = new Thread() { 2 @Override 3 public void run() { 4 while (!this.isInterrupted()) { //失效的中断检测 5 try { 6 System.out.println("sleep().."); 7 TimeUnit.SECONDS.sleep(2); 8 } catch (InterruptedException e) { //抛异常就会清空中断状态 9 System.out.println("InterruptedException...."); 10 } 11 } 12 } 13 }; 14 t1.start(); 15 TimeUnit.SECONDS.sleep(2); 16 t1.interrupt();
由于当InterruptedException异常被抛出之后,中断状态将被复位,所以当再次使用Isinterrupted/interrupted判断时,将仍然是非中断状态,线程将不能得到停止。
另一方面,如果线程运行时代码不存在阻塞的代码时,并且没有检测线程中断状态的机制,那么就算调用了interrupt方法中断该线程,该线程也不会得到任何响应,如下面的代码将无法中断非阻塞状态的线程:
1 Thread t1=new Thread(){ 2 @Override 3 public void run(){ 4 while(true){ 5 System.out.println("未被中断"); 6 } 7 } 8 }; 9 t1.start(); 10 TimeUnit.SECONDS.sleep(2); 11 t1.interrupt(); 12 13 /** 14 * 输出结果(无限执行): 15 未被中断 16 未被中断 17 未被中断 18 ...... 19 */
所以,综上情况,最好的中断运行时代码示例如下这种形式:
1 public void run(){ 2 try { 3 //判断当前线程是否已中断,注意interrupted方法是静态的,执行后会对中断状态进行复位 4 while (!Thread.interrupted()) { 5 TimeUnit.SECONDS.sleep(2); 6 } 7 } catch (InterruptedException e) { 8 9 } 10 }
总结:
| 方法名 | 类型 | 作用 | 是否复位标记 |
| public void interrupt() | 实例方法 | 中断线程 | 当一个线程处于被阻塞状态或者试图执行一个阻塞操作时,使用interrupt()方式中断该线程,将会抛出一个InterruptedException的异常,同时中断状态将会被复位(由中断状态改为非中断状态) |
|
public boolean isInterrupted() |
实例方法 | 判断线程是否被中断 | |
|
public static boolean interrupted() |
静态方法 | 判断线程是否被中断 | 中断状态将会被复位 |
特别的:抛出 InterruptedException异常的同时也会清空中断状态
2 线程合并Join
Thread类的非静态方法,其作用:在A线程执行过程中如果调用了另外一个线程对象B的join方法B.join(),那么A线程后面的代码必须要等待B线程执行结束之后才能继续往下执行。B.Join之后,A线程进入阻塞状态,并且不会释放所占用的资源,当B线程运行结束,A线程重新进人可运行状态,等待被操作系统调度执行。
1 public static void main(String[] args) throws InterruptedException { 2 Thread t = 创建t线程实例. 3 t.start();//t线程启动 4 t.join();//合并t线程到当前线程(main线程) 5 System.out.println("这里的代码必须等到t线程执行结束之后才会得到执行。"); 6 }
3 线程让步yield
Thread类的静态方法,其作用是:使当前线程出让CPU占有权,重新退回到可运行状态,而不是阻塞状态,并不会释放所占用的资源。当线程通过yield让出CPU之后,操作系统先检测是否存在和当前线程同优先级的处于可运行状态的线程,如果有,则调度这些线程执行,如果没有,则继续调度执行当前线程。
sleep方法允许较低优先级的线程获得运行机会,但yield方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。
yield只是使当前线程重新回到可执行状态,所以执行yield的线程在进入到可执行状态之后可能会马上又被调度执行,因为yield只能使同优先级的线程有执行的机会。
4 线程优先级
Thread优先级被划分1~10个级别,Thread类定义了3个整形常量:10-MAX_PRIORITY、5-NORM_PRIORITY、1-MIN_PRIORITY分别用于表示支持的最高优先级,正常优先级和最低优先级。NORM_PRIORITY 是默认优先级。
一个线程被创建时,如果没有指定优先级,则除了可以是默认值5,还可以继承父线程的优先级,当然也可以通过setPriority方法改变优先级。
优先级从设计上来说是为了达到在多线程环境中,使高优先级的线程比低优先级的线程获得更多的运行机会,但是一般不能依靠线程的优先级来决定线程的执行顺序,因为优先级需要操作系统的支持,有些操作系统会直接忽略优先级的设置,因此在实际开发中,尽量不要去设置一个线程运行时的优先级,因为结果可能不是你想要的那样。
5 守护线程设置setDaemon
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)。通过调用setDeamon可以将某个用户线程标记为守护线程,故用户线程和守护线程没有本质区别,唯一的区别在于,当所有User Thread(用户线程)全部执行结束之后,JVM虚拟机将会立即退出,而并不会等待守护线程执行结束。
1. setDaemon(true)方法的调用必须在调用start()之前设置,不能把正在运行的用户线程设置为守护线程,否则会抛出一个IllegalThreadStateException异常。
2. 在Daemon守护线程中产生的新线程也是Daemon守护线程。
守护线程的宗旨在于为其他用户线程的运行提供服务,比如JVM垃圾回收GC线程、内存管理线程,还有数据库应用中的连接池相关的监控线程,WEB应用中转发用户请求给实际工作线程的调度线程也可以设置为守护线程。
6 线程异常处理器setUncaughtExceptionHandler
当线程运行期间出现了没有被捕获的异常时,如果事先设置了异常处理器那么JVM虚拟机将会自动回调该异常处理器。线程类有两个设置异常处理器的方法:
一个是Thread类实例方法setUncaughtExceptionHandler,
一个是Thread类静态类方法setDefaultUncaughtExceptionHandler。
当出现未捕获的异常时,如果设置了实例异常处理器,那么JVM会首先直接调用它,如果没有,但是设置了类默认异常处理器,则JVM再调用它。

浙公网安备 33010602011771号