上文(深入理解Java多线程(1.5)- Java线程相关方法(1))介绍了线程Thread类和Object类的线程操作的基本方法。本文要介绍:线程优先级,守护线程,如何结束一个线程。
线程优先级
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级;
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~10之间,也可以使用Thread类提供的三个静态常量:
MAX_PRIORITY =10 MIN_PRIORITY =1 NORM_PRIORITY =5
例子代码,如下:
public class Test1 { public static void main(String[] args) throws InterruptedException { new MyThread("高级", 10).start(); new MyThread("低级", 1).start(); } } class MyThread extends Thread { public MyThread(String name,int pro) { super(name);//设置线程的名称 setPriority(pro);//设置线程的优先级 } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + "线程第" + i + "次执行!"); } } }
从执行结果可以看到 ,一般情况下,高级线程更显执行完毕。
注意一点:
虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。
守护线程
守护线程与普通线程写法上基本没啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
setDaemon方法详细说明:
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为 true,则将该线程标记为守护线程。抛出:
IllegalThreadStateException - 如果该线程处于活动状态。 SecurityException - 如果当前线程无法修改该线程。
/** * Java线程:线程的调度-守护线程 */ public class Test { public static void main(String[] args) { Thread t1 = new MyCommon(); Thread t2 = new Thread(new MyDaemon()); t2.setDaemon(true); //设置为守护线程 t2.start(); t1.start(); } } class MyCommon extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("线程1第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyDaemon implements Runnable { public void run() { for (long i = 0; i < 9999999L; i++) { System.out.println("后台线程第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
后台线程第0次执行! 线程1第0次执行! 线程1第1次执行! 后台线程第1次执行! 后台线程第2次执行! 线程1第2次执行! 线程1第3次执行! 后台线程第3次执行! 线程1第4次执行! 后台线程第4次执行! 后台线程第5次执行! 后台线程第6次执行! 后台线程第7次执行!
从上面的执行结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台线程时候一定要注意这个问题。
如何结束一个线程
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit 这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法。
- 正常执行完run方法,然后结束掉;
- 控制循环条件和判断条件的标识符来结束掉线程;
比如run方法这样写:只要保证在一定的情况下,run方法能够执行完毕即可。而不是while(true)的无限循环。
class MyThread extends Thread { int i=0; @Override public void run() { while (true) { if(i==10) break; i++; System.out.println(i); } } } 或者 class MyThread extends Thread { int i=0; boolean next=true; @Override public void run() { while (next) { if(i==10) next=false; i++; System.out.println(i); } } } 或者 class MyThread extends Thread { int i=0; @Override public void run() { while (true) { if(i==10) return; i++; System.out.println(i); } } }
诚然,使用上面方法的标识符来结束一个线程,是一个不错的方法,但其也有弊端,如果 该线程是处于sleep、wait、join的状态时候,while循环就不会执行,那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了。
所以,此时可以使用interrupt这个巧妙的方式结束掉这个线程。我们先来看看sleep、wait、join方法的声明:
public final void wait() throws InterruptedException public static native void sleep(long millis) throws InterruptedException public final void join() throws InterruptedException
可以看到,这三者有一个共同点,都抛出了一个InterruptedException的异常。在什么时候会产生这样一个异常呢?
每个Thread都有一个中断状状态,默认为false。可以通过Thread对象的isInterrupted()方法来判断该线程的中断状态。可以通过Thread对象的interrupt()方法将中断状态设置为true。
当一个线程处于sleep、wait、join这三种状态之一的时候,如果此时他的中断状态为true,那么它就会抛出一个InterruptedException的异常,并将中断状态重新设置为false。
看下面的简单的例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); } } class MyThread extends Thread { int i=1; @Override public void run() { while (true) { System.out.println(i); System.out.println(this.isInterrupted()); try { System.out.println("我马上去sleep了"); Thread.sleep(2000); this.interrupt(); } catch (InterruptedException e) { System.out.println("异常捕获了"+this.isInterrupted()); return; } i++; } } }
测试结果:
1 false 我马上去sleep了 2 true 我马上去sleep了 异常捕获了false
可以看到,首先执行第一次while循环,在第一次循环中,睡眠2秒,然后将中断状态设置为true。当进入到第二次循环的时候,中断状态就是第一次设置的true,当它再次进入sleep的时候,马上就抛出了InterruptedException异常,然后被我们捕获了。然后中断状态又被重新自动设置为false了(从最后一条输出可以看出来)。
所以,我们可以使用interrupt方法结束一个线程。具体使用如下:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(3000); thread.interrupt(); } } class MyThread extends Thread { int i=0; @Override public void run() { while (true) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("中断异常被捕获了"); return; } i++; } } }
多测试几次,会发现一般有两种执行结果:
0 1 2 中断异常被捕获了
或者
0 1 2 3 中断异常被捕获了
这两种结果恰恰说明了,只要一个线程的中断状态一旦为true,只要它进入sleep等状态,或者处于sleep状态,立马回抛出InterruptedException异常。
第一种情况,是当主线程从3秒睡眠状态醒来之后,调用了子线程的interrupt方法,此时子线程正处于sleep状态,立马抛出InterruptedException异常。
第二种情况,是当主线程从3秒睡眠状态醒来之后,调用了子线程的interrupt方法,此时子线程还没有处于sleep状态。然后再第3次while循环的时候,在此进入sleep状态,立马抛出InterruptedException异常。
继续阅读:1. Java多线程之线程基础
深入理解Java多线程(1.1)- 进程和线程的区别
深入理解Java多线程(1.2)- 创建线程之Runnable和Thread区别
深入理解Java多线程(1.3)- Java线程的生命周期
深入理解Java多线程(1.4)- Java线程之start和run区别
深入理解Java多线程(1.5)- Java线程相关方法(1)
深入理解Java多线程(1.6)- Java线程相关方法(2)
回到目录:深入理解Java多线程 - 教程系列详解
链接:https://www.jianshu.com/p/468c660d02da