Java中的多线程学习一:线程的状态和属性

 

为了使CPU的时间利用率更高,就有了多进程和多线程机制。如果是单个CPU,那么其实在微观中,还是一次执行一个线程,但是在宏观中,由于CPU的速度很快,就给了我们并发的感觉。

 

在Java中,实现多线程的类是 Thread 类。定义Thread对象有两种方法,一种是直接继承Thread,另一种是实现Runable接口。两者都需要写 run 方法。而且,无论是哪一种方法,生成的线程都是 Thread。也就是说,其实线程类只有一种,那就是Thread。

 

线程有线程自己的特性,多线程提高了CPU利用率的同时,也带来了一些问题。因此,在学习多线程,其实就是学习多线程编程多带来哪些问题,以及如何去解决这些问题,使得多线程能够按照我们的意愿来执行。

 

首先,线程有自己的状态,就像水也有自己的状态一样。水有气态,固态和液态;线程也有自己的状态,除了新建和死亡状态,就有 等待/阻塞准备 、和运行 状态。 同样的,这些状态可以转换。线程个状态的转换是:准备可以进入运行,运行也可以进入准备;运行还可以进入等待/阻塞;但是等待/阻塞不能进入运行,等待/阻塞可以进入准备状态。

 

知道了线程有这些状态,再加上控制线程的函数,我们就可以控制线程了,这就叫作线程的调度。

 

  • 新建 :就是利用Thread的构造函数定义一个线程,从此,线程的生命开始了。
  • 准备 :当执行了Thread中的strat方法,及使得线程进入准备状态,随时可以进入运行状态。
  • 运行 :当操作系统的调度器把CPU分给了这个线程,这个线程就进入了运行状态,也就是执行我们在线程中定义的run方法里面的内容。
  • 等待/阻塞
    1. 当一个线程的继续执行需要一个条件,但是该条件还不成熟,需要调度器来通知。而等待调度器发出通知的这段时间内,线程就进入了等待状态。
    2. 当一个线程访问一个资源的时候,这个资源正被另外的线程访问,而这个资源是加了锁的,之允许一个线程访问。这时候,线程就进入了阻塞状态。
  • 死亡:当线程的run方法运行完之后,线程死亡,这是正常死亡;当线程方发生异常而没有捕获时,也会死亡,这是非正常死亡。不管是正常死亡还是非正常死亡,都是死亡

 

线程除了有状态之外,还有自己的属性,这是很自然的,每个对象都有自己的属性。下面是一些重要属性:

 

  • 每个线程都有自己的优先级 。Java中的线程有10个优先级。但是结合操作系统来说,每个操作系统的线程优先级的数量各不相同。所以,Java提供了三个常量,分别是MIN_PRIORITY,NORM_PRIORITY,MAX_PRIORITY。
  • 守护属性 :如果线程被标志为守护属性,那么这个线程就是守护线程 。守护线程不能阻止程序的退出,只要没有主线程,程序就结束,而不管守护线程的运行状态。所以,我们一般不在守护线程中做一些主要工作。
    设置线程的守护属性函数为:setDaemon(true)
  • 未捕获的异常处理器 。前面说了,如果线程抛出的异常没有被捕获,线程就会死亡。这就意味着,在设计线程时,要重视捕获异常。但是,如果没有捕获异常,或者出现了一个异常不到的异常,是不是线程就马上死亡呢?当然不是。
    线程有一个重要的属性,就是未捕获的异常处理器
    使用这个方法设置:setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)

    如果设置了这个属性,就表示如果有异常没有被捕获,那么就会启动这个处理器来处理这个异常,这样,就加强了我们都线程的处理能力。

    这个方法中的传入参数:Thread.UncaughtExceptionHandler是个什么东西呢?

    原来,UncaughtExceptionHandler是一个接口。在JDK中了解到:里面只有一个方法需要实现,即unCaughtException(Thread t, Throwalbe e)。

    于是,如果在线程中有未被检测的异常中抛出时,线程就会自己查找自己的这个属性,也就是调用getUncaughtExceptionHandler。得到UncaughtExceptionHandler之后,再把该线程和抛出的异常传到这个处理器的unCaughtException方法中,进行异常处理。

    因此,我们就可以自己制作线程的未捕获的异常处理器,也就是只要实现UncaughtExceptionHandler接口即可。

    可是,我们必须为每个线程都设置这个为捕获的异常处理器属性,是不是太麻烦呢?所以,Java又帮了我们一个忙。

    如果我们忘了为每个线程设置UncaughtExceptionHandler的时候,是不是线程抛出为捕获的异常时,就会死亡呢?答案是否定的。当线程抛出未捕获的异常时,而线程又没有设置处理器时,怎么办呢?

    这时候,那么这时候的为捕获的异常处理器就变成了ThreadGroup,就是线程组(什么是ThreadGroup,等会再说)。因为线程组实现了UncaughtExceptionHandler。所以,ThreadGroup中也有uncaughtException方法,所以,也可以使用这个ThreadGroup作为处理器,

    如果调用了ThreadGroup中的uncaughtException,那么会发生什么?

    首先,会查看这个ThreadGroup有没有父线程组,如果有,则调用父线程组的uncaguhtException方法
    否则,则使用全局的线程默认未捕获的异常处理器。
    如果全局的线程默认为捕获异常处理器没有定义。再看该线程没有捕获的异常是不是ThreadDeath的一个实例,如果是,则设么都不做。
    如果不是ThreadDeath,错误信息会被输出到System.err上。

    那么,如何设置线程的全局默认为捕获异常处理器呢?

    调用Thread的一个静态方法:
    Thread.sedDeafaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

    至此,线程对为捕获异常的处理的过程就是这样:

    1. 找出线程自己的处理器
    2. 如果找不到线程自己的处理器,则把线程所在的线程组当作处理器,主要是找到线程组中的uncaughtException方法。
    3. 如果找不到此方法,则找到此线程组所属的父线程组的这个方法。
    4. 如果还是找不到,就找线程全局默认的未捕获的异常处理器。
    5. 如果再不行,就看看这个线程没有捕获的异常是不是ThreadDeath,如果是,就什么都不做
            如果不是,就在System.err中输出错误信息。

 

那么,线程组又是什么东东呢?说白了,就是线程的一个集合。在Java中,只有一个默认的线程组,如果需要其他的线程组,就需要自己创建。除了默认 的线程组,其他的线程组都需要有一个父线程组。也就是说,Java中的线程组是树形结构的。

 

Java核心技术卷 写道

“但是从Java SE 5.0起引入了更好的特性用于线程集合的操作,不要在自己的程序中使用线程组。”

 

所以,对线程组的研究就暂告一段落了

 

------------------

听者有心 原创

你可能感兴趣的:(java,jdk,多线程,thread,编程)