从零学习JAVA多线程(二):线程的状态和属性

来源:互联网 发布:淘宝详情页文字大小 编辑:程序博客网 时间:2024/05/29 13:39

  • 线程的六种状态
    • New状态
    • Runnable状态
    • BlockedWaiting和Time waiting状态
    • Terminated状态
    • 线程状态转化图
  • 线程属性
    • 线程优先级
    • 守护线程
    • 线程组
    • 未捕获异常处理器

线程的六种状态

状态 释义 New 新创建 Runnable 可运行 Blocked 被阻塞 Waiting 等待 Timed waiting 计时等待 Terminated 被终止

如果想要获取一个线程的状态,可以使用getState()方法。
见代码段:

package multithreadTest;/** * Created by Vivi on 2017/10/6. */public class MultiTest {  public static void main(String args[]) {    RunnableTest runnableTest = new RunnableTest();    Thread runnableThread = new Thread(runnableTest);    System.out.println("new操作之后的线程状态是:"+runnableThread.getState());    runnableThread.start();    System.out.println("start操作之后的线程状态是:"+runnableThread.getState());  }}

打印的内容是:

new操作之后的线程状态是:NEWstart操作之后的线程状态是:RUNNABLE继承runnable接口创建一个新线程

最后一句打印出的内容是RunnableTest中run()方法的内容(RunnableTest类的代码见上一篇文章)。根据打印顺序,我们可以很清楚的看到,当我们new一个thread的时候,并没有执行run()中的代码,new仅仅是创建了一个线程。

New状态

这个状态最简单,当执行

    Thread runnableThread = new Thread(runnableTest);

代码时,runnableThread就处在new状态,不执行任何动作。

Runnable状态

从测试代码也能看出来,当我们执行start()之后,线程的状态就会变成 RUNNABLE 。开始准备执行run()方法中的代码块。
但需要注意的是,一个处于Runnable状态的线程不一定正在运行。这个是操作系统的线程调度策略决定的。我们知道操作系统负责分配cpu的时间片,一个线程只有在分配到时间片的时候才能使用cpu,只有在使用cpu的线程才处于真正的运行状态。没有分配到时间片的线程就没有处于真正的运行状态。

Blocked、Waiting和Time waiting状态

这三种状态有一个共同点,就是处于这些状态的线程是停止活动的,不执行任何的的代码,占用非常小的系统资源。而区别在于停止活动或者继续活动的条件。

  • Blocked:一个线程试图获取一个对象锁,而这个锁恰好被其他线程持有的时候,就是想要锁拿不到的时候,它就停下来,暂停所有活动,直到获得这个锁重新开始活动。在等待锁的线程就是阻塞状态线程。
  • Waiting:一个线程运行到一段代码的时候,这个代码告诉它,你必须等到别的线程完成一件什么事情之后才能继续执行,然后这个线程就停下来,等线程调度器给它通知,在等待过程的线程就是一个等待线程。
  • Time waiting:计时等待状态比等待状态多了一个时间限制,也就是说计时等待状态的线程也是因为等待通知而停止活动,但它却不是无限制的等待,它有一个 超时时间参数 ,一旦停止活动的时间超过设置的时间参数,它将停止等待直接返回。

这些状态的线程一旦满足继续执行的条件,就会重新变成Runnable状态,等待系统分配cpu时间片继续执行代码。

Terminated状态

线程一旦进入被终止状态也就是说线程挂掉了,不能再完成任何任务。
有两种操作会导致线程死亡。
- run()方法执行完毕正常退出,线程死亡
- 因为一个没有捕获的异常导致run()方法异常终止,线程挂掉了
前一种情况非常的正常,后一种就需要debug了。

线程状态转化图

偷懒截张图

线程属性

说完线程的状态,还要分析线程的属性,其实就是几个听烂的名词:线程优先级、守护线程、线程组和处理未捕获异常的处理器。

线程优先级

优先级对应的属性在Thread类中是 priority,int型,默认值5.最小1最大10。可以通过setPriority()方法设置一个合法值。
在前面分析线程属性的时候,我们提到了 线程调度器,线程调度器在线程进行调度分配资源的时候,就会依据线程的优先级工作。优先级高的先拿到资源或者先运行,优先级低的必须等到优先级高的线程执行完毕或者不再需求资源的时候才能工作。但这也不是绝对的,因为优先级也会受操作系统的影响,比如windows有7个优先级别,那么JVM就会把10个优先级映射到7个操作系统优先级上,以操作系统优先级为准;再比如,很多linux系统就完全没有优先级,那么我们自己设置的线程优先级也就没有任何意义。
不过大部分情况下,优先级这个属性还是会起作用的,那么我们就要谨慎一点设置优先级,比如你给了一个线程很低的优先级,那么它可能永远都被挂起…..
如果想要设置同一优先级线程的执行优先度,可以使用yield()方法,它会设置当前线程为让步状态,如果有比它优先级高的或者同优先级,就别的优先级先执行。

守护线程

守护线程对应的属性在Thread类中是daemon,boolean型,默认为false。可以通过setDaemon()设置合法值。
如果daemon这个值是false,那这个线程就是用户线程(普通线程),为true就是一个守护线程。
守护线程相当于程度的后勤人员,他们就静静的待在那里,为我们程序中的其他线程提供服务。比如我们设置一个用来计时的线程作为守护线程,别的线程都从这个线程获取时间信息。
在JVM中,垃圾回归、内存管理都属于守护线程,很多为程序或者系统提供基础服务的线程都是守护线程。
如果我们的程序中只剩下守护线程在运行,那么JVM就会认为已经完成任务,自动退出。这就是说,守护线程随时可能会中断或者被终止,为了避免系统出现死锁,一个随时可能中断或者被终止的程序不应该访问任何固定资源(文件、数据库)或者IO设备。
看下面这段代码:

package multithreadTest;/** * Created by Vivi on 2017/10/7. */public class DeamonRunnable implements Runnable {  public void run() {    try {      Thread.sleep(10000);    }catch (InterruptedException e){      e.printStackTrace();    }finally {      System.out.println("这是一段不会被打印的文字");    }  }}

测试入口

package multithreadTest;/** * Created by Vivi on 2017/10/6. */public class MultiTest {  public static void main(String args[]) {  DeamonRunnable deamonRunnable = new DeamonRunnable();   Thread thread = new Thread(deamonRunnable);   thread.setDaemon(true);   System.out.println("下面启动这个线程");   thread.start();  }}

打印输出:

下面启动这个线程

这说明在一个守护线程中,我们没有办法确保线程占有的资源能够被释放,那唯一的办法就是,别碰资源。

线程组

线程组是一个比较好理解的概念,从系统顶层看下去,我们可以看到这样一棵树:
线程组示意图
线程组的作用是批量的管理线程或者批量的管理线程组,让数目可能比较庞大的线程们有组织有纪律起来,方便我们对于线程的操作。
当我们新建一个线程的时候,这个线程默认是和当前线程一组的。

package multithreadTest;/** * Created by Vivi on 2017/10/6. */public class MultiTest {  public static void main(String args[]) {    System.out.println("当前线程名称:" + Thread.currentThread().getName());    System.out.println("当前线程组名称:" + Thread.currentThread().getThreadGroup().getName());    System.out.println("当前线程组数量:" + Thread.currentThread().getThreadGroup().activeCount());    Thread thread = new Thread();    thread.start();    System.out.println("新线程名称:" + thread.getName());    System.out.println("新线程所属线程组名称:" +thread.getThreadGroup().getName());    System.out.println("新线程所属线程组中线程数量:" + thread.getThreadGroup().activeCount());  }}

打印输出如下:

当前线程名称:main当前线程组名称:main当前线程组数量:2新线程名称:Thread-0新线程所属线程组名称:main新线程所属线程组中线程数量:3

也简单演示一下怎么设置线程组:

package multithreadTest;/** * Created by Vivi on 2017/10/6. */public class MultiTest {  public static void main(String args[]) {    System.out.println("当前线程名称:" + Thread.currentThread().getName());    System.out.println("当前线程组名称:" + Thread.currentThread().getThreadGroup().getName());    System.out.println("当前线程组数量中线程数量:" + Thread.currentThread().getThreadGroup().activeCount());    ThreadGroup threadGroup = new ThreadGroup("新的线程组");    Thread thread = new Thread(threadGroup, "线程1");    thread.start();    System.out.println("新线程名称:" + thread.getName());    System.out.println("新线程所属线程组名称:" + thread.getThreadGroup().getName());    System.out.println("新线程组数量中线程数量:" + thread.getThreadGroup().activeCount());    Thread thread2 = new Thread(threadGroup, "线程2");    thread2.start();    System.out.println("新线程名称:" + thread2.getName());    System.out.println("新线程所属线程组名称:" + thread2.getThreadGroup().getName());    System.out.println("新线程组数量中线程数量:" + thread2.getThreadGroup().activeCount());  }}

输出如下:

当前线程名称:main当前线程组名称:main当前线程组数量中线程数量:2新线程名称:线程1新线程所属线程组名称:新的线程组新线程组数量中线程数量:1新线程名称:线程2新线程所属线程组名称:新的线程组新线程组数量中线程数量:2

关于线程组几个比较重要的Tips:
- 我们可以使用interrupt()方法批量停止线程组(threadGroup.interrupt())
- 所有线程的根线程组都是system线程组
- 很多书里面建议我们少用线程组

未捕获异常处理器

前面说到一个线程在两种情况下状态可能会变成Terminated,一种是正常结束,一种就是出现了没有捕获的异常。
那就要求我们尽可能的捕获所有的异常,异常捕获到了就得处理啊。JVM就帮我们提供了一个未捕获异常处理器,这个处理器必须实现Thread.UncaughtExceptionHandler接口(我们使用的时候不需要关注)。
使用的逻辑是这样子的,我们用setUncaughtExceptionHandler()方法为线程设置一个处理器,如果不设置的话,当前线程就会使用线程所属线程组的处理器和解决问题。
ThreadGroup类实现了Thread.UncaughtExceptionHandler接口,对应的uncaughtException方法会做下面的这些事情:
- 如果该线程组有父线程组, 那么父线程组的uncaughtException 方法被调用。
- 否则, 如果Thread.getDefaultExceptionHandler 方法返回一个非空的处理器, 则调用该处理器。
- 否则, 如果Throwable 是ThreadDeath 的一个实例, 什么都不做。
- 否则, 线程的名字以及Throwable 的栈轨迹被输出到System.err 上。

未捕获异常处理器深入考究的话有不少东西值的琢磨一下,这个地方建议翻一下《Java多线程编程核心技术》或者搜索一下大神们的博文,但在开始学习多线程的时候,我们知道有这么个东西就可以了。

这篇博文就到这里,下一篇讲线程同步。

**附上版权声明:
原作者:Vi_error,博客地址:Vi_error.nextval
转载请保持署名和注明原地址**____

阅读全文
0 0
原创粉丝点击