java线程(上)——线程状态及属性详解

在操作系统中,我们通过分时的方法在CPU上不断地切换处理多个进程任务,给人并行处理的感觉,这种方法在操作系统中叫做多任务。多任务在较低层次上扩展出多线程的概念,也就是指一个程序同时执行多个线程。这种可以同时运行一个以上的线程的程序,我们叫做多线程程序。

(一)创建线程

1.创建线程实例

百闻不如一见,与其先说线程的各种状态,不如我们先直接演示如何创建一个新的线程。

package Thread;

/**
 * 
 * @author QuinnNorris
 * 创建线程实例
 */
public class NewThread {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Runnable r = new Run();
        //创建一个类对象
        Thread th = new Thread(r);
        //由Runnable创建一个Thread对象
        th.start();
        //调用start方法,运行新的线程,即运行的是th中的run方法
    }

}

我们再看看Run这个实现了Runnable接口的类的情况:

package Thread;

/**
 * 
 * @author QuinnNorris
 * Run类实现了Runnable接口
 */
public class Run implements Runnable {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

创建并运行一个线程大概是这样的,我们先创建一个类去实现Runnable接口,在其中实现Runnable接口中唯一的run方法,这方法就是线程启动后自动调用的方法。之后我们在主类中创建一个Run类的对象,将这个对象作为构造器的参数传入Thread对象中,之后我们用Thread对象的参数调用start方法,start方法会自动的调用run方法,开始执行run方法中的语句。这时便创建并启动了一个线程。

2.start方法和run方法

需要注意的是,我们不能直接调用run方法因为这样不会创建一个新的线程,而是调用到一个已经存在的线程中间,start会给你创建新线程,并自动调用run。我们在任何情况下都不需要自己手动调用run方法。
说到这里我们再来看一下API中的start方法:

public void start( )
使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

3.Runnable类

我们已经知道了如何按部就班的创建一个线程,但这并不能满足我们的好奇心,我们要直接将这两个类一探究竟,看看其中到底有什么方法和属性。Runnable类中没有属性,只有一个run方法。这个接口应该由那些打算通过某一线程执行其实例的类来实现。而Thread类却比较的复杂。

(二)Thread类

1.Thread类实现了Runnable接口

所以上例中Run类有一个理所当然的写法就是继承Thread类,因为Thread实现了Runnable,这两种写法实际上都是重写run方法。但是面试题非常喜欢问你:Thread和Runnable这两种实现多线程的方式哪种好?这时应该回答Runnable方法好的。原因也很简单,继承只能有一个,接口可以有很多,Runnable是接口不阻碍这个类继承的灵活性。

2.Thread构造器

Thread的构造器有很多,参数主要有这四种:

public Thread(ThreadGroup group,Runnable target, String name,long stackSize)

其他的构造器都是这四种参数的组合,那么我们来看一下这四种参数的含义:

1.ThreadGroup group

ThreadGroup是线程组的意思,它可以对一批线程进行分类管理。对线程组的控管理,即同时控制线程组里面的这一批线程。用户创建的所有线程都属于指定线程组,如果没有显示指定属于哪个线程组,那么该线程就属于默认线程组(即main线程组)。默认情况下,子线程和父线程处于同一个线程组。只有在创建线程时才能指定其所在的线程组,线程运行中途不能改变它所属的线程组,也就是说线程一旦指定所在的线程组,就直到该线程结束。线程组与线程之间结构类似于树形的结构,线程只能访问自己线程组的信息,不能访问父线程组或其他线程组内的信息。

2.Runnable target

target就是其run方法被调用的对象。如果这个参数是null,则这个Thread类中要自己重写run方法,否则这个Thread类的run方法是调用这个Runnable类中的run方法。

3.String name

每个线程都有一个自己的名字,其实这个参数是很少被使用的,有的时候我们命名一个线程并不用这种形式。但是如果我们不去填这个参数的话,线程会自动的新生成一个名称。自动生成的名称的形式为 “Thread-“+n ,其中的 n 为整数。

4.long stackSize

这是一种具有平台依赖性的参数,stackSize能指定堆栈的大小。 在某些平台上,指定一个较高的 stackSize 参数值可能使线程在抛出 StackOverflowError 之前达到较大的递归深度。stackSize 参数的值与最大递归深度和并发程度之间的关系细节与平台有关。在某些平台上,stackSize 参数的值无论如何不会起任何作用。 作为建议,可以让虚拟机自由处理 stackSize 参数。

Thread中还有很多很多实用的方法,我们在涉及到具体概念的时候再给予介绍。

(三)中断线程

我们既然创建了线程,那么这个线程在什么时候终止呢?有两种情况:

  1. 当线程的run方法执行方法体中最后一条语句后,正常退出而自然死亡。
  2. 出现了在方法中没有捕获的异常,此时终止run方法,意外死亡。

从这两点中,我们可以看出,其实我们并没有特殊的手段可以人为的去在中间干涉线程的中断(Thread中的stop方法或许有这种作用,但是这个方法已经被废弃,我们不要使用它)。虽然没有强制结束线程的方法,但是我们可以用interrupt方法请求终止线程。要注意“请求”这个词,没有任何语言方面的表述是要求一个被中断的线程应该被终止。我们去用interrupt方法中断一个线程不过是引起他的注意。被中断的线程可以决定如何去响应这个中断的请求。

当对一个线程调用interrupt方法时,线程的中断状态将被置位。这个中断状态是每个线程都有的boolean标志。每个线程都会不时地检查这个布尔值,判断这个线程是否被中断。
我们可以使用:

Thread.currentThread().isIntertrupted()

isInterrupt方法和interrupt很像,它是用来判断线程是否被中断。
我们可以通过interrupt方法来中断一个线程,但是值得注意的是,如果这个线程处于wait和sleep或者在join方法调用过程中,中断线程将会报一个InterruptedException异常,我们可以通过try-catch块来捕获这种异常。

(四)线程状态

线程一共有6种状态,这六种状态不是我们规定的,而是在Thread中的内部嵌套类State中规定的。这六种状态分别是New,Runnable,Blocked,Waiting,Timed waiting,Terminated六种,我们来逐个分析一下这几种状态的含义:

1.New 新创建的线程

这里的创建新的线程真的是仅仅new了一个线程。

new Thread(r);

创建新的线程,是指刚刚new出来的线程,这个线程没有通过start的方法来启动。

2.Runnable 可运行

那么一旦我们调用了start方法,这个线程开始工作。这是他就处于可运行状态,这个可运行状态不只是包含线程运行的时候,线程在中断的时候也被算为可运行状态。一个可运行状态的线程可能在运行也可能没在运行,我们不要因为一个线程在可运行的状态下没运行而急躁,很有可能这个线程的终止只是为了让其他的线程获得机会。

3.Blocked 被阻塞

当一个线程试图去获得一个内部锁时,但这个内部锁被其他的线程持有,这个时候,为了等待去使用这个内部锁,这个线程将会暂时处在被阻塞的状态。当其他线程释放锁的时候,这个线程获得了内部锁,并且从阻塞状态转变为非阻塞状态。

4.Wait 等待

当一个线程等待另一个线程通知调度器一个条件时,这个线程自己进入等待状态。等待状态和阻塞状态很类似,但是他们是存在本质区别的。如果另一个线程通知调度器结束,那么这个线程进行工作,等待状态也随之结束。

5.Timed waiting 计时等待

计时等待和等待是比较相似的,计时等待是表示他有一个超时参数。调用他们导致线程会进入计时等待。这个状态将一直保持到超市期满或者接收到适当的通知。相比较直接的等待,变得更加的安全。

6.Terminated 终止

线程的终止,我们在上面的线程中断中有所提及。线程终止理论上只有两种情况:当线程的run方法执行方法体中最后一条语句后,正常退出而自然死亡。2. 出现了在方法中没有捕获的异常,此时终止run方法,意外死亡。

(五)线程属性

线程中有很多的属性,尽管我在API中只看到一些其中一小部分的字段,但是线程的这种理念,在方法中也有所体现。线程的属性有以下这些:线程优先级,守护线程,处理未捕获异常的处理器。

1.线程优先级

在java中,每个线程都有一个优先级,我们需要知道的是,在没经过特殊处理的时候,所有的线程优先级都是一样的。默认的,我们把优先级分成1到10之间,高优先级的线程会先被操作。说到这里不由得让我们想起了操作系统中的进程优先级,和一些类似老化这样的名词。实际上,java虚拟机也确实是用进程的优先级来类比出线程的优先级,这样做最大的一个问题就在于,每个操作系统对于进程优先级的处理并不相同,java的线程优先级也因此而具有平台变化。实际上,我们不应该把程序的正确性依赖于线程优先级,我们应该尽量少的使用线程优先级。我们在这里介绍了线程优先级,但是我们并不建议去使用它。

2.守护线程

守护线程是指通过:

t.setDaemon(ture); //将线程转换为守护线程

这样的写法,将一个线程转换为守护线程。守护线程的作用是为其他的线程提供服务,如果其他所有的线程都被退出,只剩下守护线程,那么程序也就结束了。没有去单独运行守护线程的必要。比如说其他线程的计时器,我们就可以将它设置为一个守护线程。

3.处理未捕获异常的处理器

run方法不能抛出任何被检查,不被检测的异常会导致线程终止。在这种情况下,线程就死亡了。在这种情况下,我们需要实现一个Thread.UncaughtExceptionHandler。我们可以通过这种方式来知道,真正让我们的run意外死亡的问题在哪里。

(六)总结

在java的并发程序中,我们先了解了这些线程的状态和属性,下面我们就可以来研究关于线程之间同步的问题。如何能几个线程同时运行,让他们之间配合的好。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值