线程的状态、分类及优先级

来源:互联网 发布:nba凯尔开人球队数据 编辑:程序博客网 时间:2024/06/05 19:38

转转请注明出处:http://blog.csdn.net/yegongheng/article/details/38708765


       上一篇《线程的概念及简单实现》博文我们简单地认识了关于多线程的概念以及使用Java语言实现多线程,这算是我们对Java并发编程学习的一个入门吧,那本篇博文我们将继续更深入地学习多线程方面的知识。


线程的状态


       同样的,线程作为一项任务的执行者,从开启、运行到终结、销毁,都有它自己的生命周期。那在Java定义中,一般线程可以分为六种状态,我们通过查看Thread类源码发现,在Thread类中定义了一个线程状态的枚举,代码如下:
public enum State {        /**         * Thread state for a thread which has not yet started.         */        NEW,        /**         * ...         */        RUNNABLE,        /**         * ...         */        BLOCKED,        /**         * ...         */        WAITING,        /**         *...         */        TIMED_WAITING,        /**         * Thread state for a terminated thread.         * The thread has completed execution.         */        TERMINATED;    }
下面我们对每一种线程状态进行简要的说明:
       1. 新生(New):在Java中使用new Thread(r)来创建一个新的线程,此时线程处于新生的状态,但是程序还没有开始运行线程的代码;
       2. 可运行(Runnable):在Java中当调用start()方法后,线程进入了可运行的状态,此时线程可能正在运行,也可能没有运行,这个取决于CPU是否给当前线程提供了时间片;
       3. 阻塞(Blocked):一般在如下情况下,线程会处于阻塞状态:当线程想要获取内部的对象锁,而该锁被其它线程持有,此时线程陷入阻塞;
       4. 等待(Wait):当线程调用Thread.join()方法将CPU的执行权力(即时间片执行权力)出让给其它线程时或调用wait()方法让当前线程等待其它线程资源执行时,线程处于等待状态。阻塞状态和等待状态的区别还是比较大的。
       5. 计时等待(Timed waiting):计时等待和上面所说的等待状态类似,不同的是计时等待是在其所设置的超时时间后从等待状态自动自我唤醒,然后进入到可运行状态,而前者是需要其它线程进行手动唤醒。那一般设置定时等待的方法有sleep(long millis)、wait(long timeout)或join(long millis)。
       6. 终止(Terminated) :而线程终止或销毁一般是会在以下两种情况下发生:1. run()方法中的所有耗时操作都执行完毕,程序流正常退出;2. 在run()方法中有未捕获的异常而导致程序的非正常退出。

基本了解了线程的六种状态后,下面我们通过一张线程的状态图来进步了解一下线程六种状态之间的关系和切换过程,如图下:

线程的分类


       线程根据作用可以分为用户线程守护线程,一般我们平时用于执行具体业务逻辑的线程都是用户线程,而守护线程则一般是为正在运行的用户线程提供便利服务的。比方说JVM中的GC(垃圾回收器)线程,它就是一个守护线程,它的作用就是回收其它普通用户线程执行完成后遗留下的内存资源。那我们就会想,若在JVM中的普通用户线程执行完各自的业务逻辑后消亡,守护线程依然在运行,那JVM实例还有存在的必要吗?答案当然是 -- NO。JVM实例不会因为守护线程没有终止而继续存在,它会在所有普通用户线程终止后退出。
      那既然JVM实例并不会因为守护线程正在运行而持续保持,那为了避免操作和对象的完整性,我们不应该将一些较为重要的业务逻辑放在守护线程中执行,如文件、数据库的操作,因为它有可能在任何时候甚至在一个操作的中间发生中断。守护线程并不仅仅存在于系统级别,我们自己也可以创建守护线程,我们只需要Thread对象在调用start()方法之前调用setDaemon(boolean on)方法,将参数设置为true,那我们所创建的线程便是守护线程。

线程的优先级


       谈到线程的优先级,可能许多读者认为这部分知识并没有单独拿出来学习的必要,因为在java中,线程的优先级无非就是为线程设置从1-10之间的线程等级,在程序运行时,线程调度器会首先选择具有较高优先等级的线程。其实上面读者对线程优先级的理解并没有错,但理解得并不是很深刻和全面,因为线程的优先级是高度依赖系统的,不同的操作系统平台的线程实现机制是不同的。就拿Windows 和Linux操作系统来讲,Windows系统有7个线程优先级别,而Linux系统拥有2的31次方线程优先级别。当Java虚拟机依赖于宿主主机平台(Linux或Windows)的线程实现机制时,java线程的优先级会被映射到宿主主机平台的优先级上,对于拥有2的31次方优先等级的Linux系统来说,支持映射java 10个线程优先等级当然没有问题,但对于只有7个线程优先等级的Windows系统来说,要映射到Java的10个线程优先等级上,势必会造成很多问题,例如可能Java里面的优先级1、2会等同于Windows系统里的1等级,或是线程优先级8、9、10等同于Windows系统里的7等级。那此种情况下,Java的线程优先级的设置就没什么作用了。
       另外Java程序是运行在Java虚拟机中的,而由于虚拟机版本的差异化,也会导致线程优先级的映射产生不同的结果,例如Sun为Linux提供的Java虚拟机,线程的优先级被忽略--即所有线程具有相同的优先级。
       前面讲了那么多文绉绉的理论知识,自己都感觉有点磨叽了,下面我们来学习如何使用Java为线程设置优先级。一般我们可以通过调用setPriority(int newPriority)方法为线程设置优先级,方法中传递1-10之间的常量值,用于设定线程优先等级,1为最低优先级(在Java中可以用Thread.MIN_PRIORITY),10为最高优先级(在程序中可以用Thread.MAX_PRIORITY),如何不设置的话,默认线程等级为5(在程序中可以用Thread.NORM_PRIORITY)。下面这个实验,我们创建三个线程,模拟三个售票窗口售票,并且为每个线程设置不同的优先级,观察在设置了优先级后,每个售票窗口售票情况是否有明显的差别。我们先来看下代码,如下:
package com.androidleaf.multithreading.priority;public class ThreadPriority {public static void main(String[] args) {// TODO Auto-generated method stubMyRunnable myRunnable = new MyRunnable();Thread mThread0 = new Thread(myRunnable);//设置最高优先级,10mThread0.setPriority(Thread.MAX_PRIORITY);Thread mThread1 = new Thread(myRunnable);//默认优先级,5mThread1.setPriority(Thread.NORM_PRIORITY);Thread mThread2 = new Thread(myRunnable);//设置最低优先级,1mThread2.setPriority(Thread.MIN_PRIORITY);mThread0.start();mThread1.start();mThread2.start();}}class MyRunnable implements Runnable{private int tickets = 50000;private int threadFirst = 0;private int threadSecond = 0;private int threadThird = 0;@Overridepublic void run() {// TODO Auto-generated method stubwhile(true){synchronized (this) {if(tickets > 0){System.out.println("总票数:" + tickets + "  窗口号:"+ Thread.currentThread().getName()+ " 出售1张        剩下票数:" + --tickets);if(Thread.currentThread().getName().equals("Thread-0")){++ threadFirst;}else if(Thread.currentThread().getName().equals("Thread-1")){++ threadSecond;}else{++ threadThird;}}else{break;}}}//售票次数只打印一次if(Thread.currentThread().getName().equals("Thread-0")){System.out.println("第一售票窗口售票数:"+ threadFirst);System.out.println("第二售票窗口售票数:"+ threadSecond);System.out.println("第三售票窗口售票数:"+ threadThird);}}}

程序中,为了让测试结果更具有可靠性,我们尽量让售票数较大(这里取50000张),然后三个窗口开始售票,最后当售票结束后,统计每个窗口的售票数。我们通过数十次的测试后,取得了一组售票结果的数据,我分别取了最具有代表性的的六组数据,分别如下:
第一组售票数据结果:第一售票窗口售票数:43147第二售票窗口售票数:5429第三售票窗口售票数:1424第二组售票数据结果:第一售票窗口售票数:13290第二售票窗口售票数:35528第三售票窗口售票数:1182第三组售票数据结果:第一售票窗口售票数:45318第二售票窗口售票数:3905第三售票窗口售票数:777第四组售票数据结果:第一售票窗口售票数:16950第二售票窗口售票数:32200第三售票窗口售票数:850第五组售票数据结果:第一售票窗口售票数:44568第二售票窗口售票数:3288第三售票窗口售票数:2144第六组售票数据结果:第一售票窗口售票数:41628第二售票窗口售票数:3928第三售票窗口售票数:4444
观察上面六组售票数据结果,我们可以发现,一般的线程优先级越高的线程,它所得到的执行的机会越多,那最终所出售的票数就越多,但这也不是严格按照这个规律来进行的。比如我们看一下第二和第四组数据,我们发现,虽然第一售票窗口的线程优先级比第二窗口的线程优先级更高,但第二窗口所出售的票比第一窗口要更多。同样的,在看下第六组数据,比较第二窗口和第三窗口的售票数情况,也是一样的现象。
       至此,通过上面的实验我们可知,在我们编写多线程程序时,不能过度使用线程的优先级,特别是不能将构建正确的程序建立在依赖线程优先级的基础之上,因为程序并不保证拥有较高优先级的线程一定会有更多的执行资源。况且线程的优先级严重依赖具体操作系统和虚拟机版本,在Java跨平台开发大行其道的今天,若将使用过多优先级的Java程序部署在不同的系统平台上,会产生一些意想不到的问题。所以,以后若要使用线程的优先级,要三思啊!!

    
小结:今天我们学习了一些线程的特性,主要包括:(1)线程的各种状态;(2)线程的分类;(3)线程的优先级。我们着重讲了一下线程的各种状态的特点,并通过一张状态图清晰地了解了状态之间的关系和切换条件。还有就是分析了线程的优先级方面的知识,Java中线程的优先级严重依赖具体的操作系统和虚拟机的版本,因此我们使用的时候需谨慎。下一篇博文我们将讨论线程的中断和阻塞相关的知识,敬请期待!

0 0