为了满足多线程下每一个线程的重要主次的差异性,每一个线程启动后,实际上都会被设置一个代表优先级的数值,通常情况下,该数值是5,代表中等优先级别。本节将介绍不同优先级的线程协同工作时的不同表现及设置线程的优先级等内容。
Java线程的优先级使用int型的数值来表示,范围为1~10。其中,一个线程如果没有显式设置,则默认为5,属于中等优先级别;如果设置的数据是其他类型,或者不在1~10范围内,线程就会报错。实际上,Java线程的源代码中已经对线程的优先级做了预定义处理,可以参考Thread中的一段源代码来加深认识。参考代码如下:
由Thread源代码可以看到,Java线程已经将数值等于5作为线程优先级的默认值;另外,它还将数值等于1作为线程的最小优先级,数值等于10作为线程的最大优先级。
在Java线程中,可以使用setPriority()方法来设置线程的优先级。我们来看下面的参考代码:
上面的代码将线程01设置了数值为8的优先级,这是比较高的优先级,程序照常运行,输出I am running的信息。因为没有其他线程与之比较,所以暂时无法确定这样的较高优先级是否生效,2.3.3小节将尝试多线程下的优先级。
我们设置了线程的优先级,那多线程的情况下,是否真的就如所设置的情况一样?不妨来做一个测试。参考代码如下:
由于是多线程的运行,因此运行后的结果可能会存在多种情况,但绝大多数情况属于下面的两种参考结果之一:
读者可以多尝试几次。可以看出,在多线程的情况下,拥有最高优先级的threadMax及较高优先级thread01一般最先完成任务(有时thread01比threadMax还快,因为thread01启动start()方法的瞬间先于threadMax);而较低优先级的thread02和thread03往往最迟完成,特别是拥有最低优先级的thread03。
第1章已经简单介绍了守护线程和用户线程。作为使用者在编写代码时,绝大部分都是建立普通的用户线程。如果不加以设置和说明,一般创建的线程都默认为普通的用户线程。而守护线程用得比较少,有时接触到也未必察觉到,但守护线程的存在却有着重要的意义。Java中的垃圾回收线程(Garbage CoIIections, GC)就是一个守护线程,其不参与用户的业务逻辑,而是作为系统的内存守护者,GC会不定期地回收过期的占用内存的对象资源,释放出内存空间,合理地防止内存泄漏和溢出的问题。
守护线程:本身是一个线程,该线程几乎不处理用户的业务逻辑,而是作为一个守护者,守护着一组用户线程,并且会为这些线程提供便利的服务。当最后一个用户线程终止时,该守护线程才算完成使命。在Java中需要通过Thread.setDaemon(true)方法来显式设定守护线程。
接下来,我们将以实际的示例来说明用户线程和守护线程。下面先通过一组普通线程的组合运行,来看运行的情况,参考代码如下。
DoOnceThread线程类,即只做一件事的线程:
CommonRunnabIeThread线程类,属于普通用户线程的一种:
main()方法,启动线程类:
运行的参考结果如下:
虽然main()方法所在的运行类是DaemonThreadTest01,但实际上其并未明确设置守护线程,后面的示例中出现包含守护线程的DaemonThreadTest02类会与之对比。看运行的参考结果,一共有两个线程进行了输出,其中一个是A001,另外一个是B001。其中,B001线程是在A001线程的run()方法中进行实例化和运行的,A001把B001线程带起后就终止了。而B001线程由于有whiIe(true)循环标识,即使A001线程终止,B001也会一直循环输出“Hi~,我是普通的用户线程B001哟,我每隔一秒就和大家打一声招呼。”的相关内容。
如果将B001线程进行修改,即将B001线程设置为A001的守护线程,那么会怎么样?不妨继续看下面改版后的示例,参考代码如下。
WithDaemonFriendThread线程类,其中包含另一个守护线程:
可以看出,比起之前的DoOnceThread线程类,WithDaemonFriendThread线程类几乎只是多了一个myFriendThread.setDaemon(true)方法,将它的朋友(线程B002)设置为守护线程。
DaemonRunnabIeThread线程类是一个守护线程类,代码如下:
main()方法,启动线程类:
参考的运行结果如下:
包含whiIe(true)永恒循环方法的B002线程本应该一直循环下去,但它在A002线程输出一句话结束且终止后也随之终止。
这是因为A002线程在其run()方法中将它的朋友B002线程设置为守护线程,即B002线程应该要守护A002线程。如果被守护的A002线程不存在,那么B002线程在目前时刻就没有其他需要守护的线程,B002线程也会非常快速地消失。
为了验证B002线程作为A002线程的守护线程,到底是否会伴随A002线程的终止而终止,我们可以适当地调整A002线程的生存时间。参考代码如下:
我们将A001线程睡眠多6秒后再终止,观察运行结果有何变化。运行的参考结果如下:
可以看到,A002线程的生命周期延长后,B002线程的打招呼次数增多了。守护线程B002总是比其要守护的普通用户线程A002多运行一段时间,即B002线程要守护好A002线程的完整的生命周期。
设置守护线程时一般需要注意以下几点。
(1)守护线程中尽量不要书写业务逻辑相关的内容,因为被守护的线程一旦终止,守护线程就会几乎立刻终止,甚至是在执行了一半的任务当中也会终止,如上面示例中的永恒循环照样会被终止。
(2)守护线程应该书写与被守护线程的生命周期如线程状态或属性,以及被守护线程占用的资源释放与回收等相关的内容。例如,GC,它作为一个守护线程,一般不参与业务逻辑,但它会不断检测被守护线程的内存使用情况。
(3)守护线程要在其启动之前设置好,即setDaemon(true)方法要出现在与线程启动有关的start()方法之前,否则会抛出IIIegThreadStateException异常。