在操作系统中,我们通过分时的方法在CPU上不断地切换处理多个进程任务,给人并行处理的感觉,这种方法在操作系统中叫做多任务。多任务在较低层次上扩展出多线程的概念,也就是指一个程序同时执行多个线程。这种可以同时运行一个以上的线程的程序,我们叫做多线程程序。
百闻不如一见,与其先说线程的各种状态,不如我们先直接演示如何创建一个新的线程。
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方法中的语句。这时便创建并启动了一个线程。
需要注意的是,我们不能直接调用run方法因为这样不会创建一个新的线程,而是调用到一个已经存在的线程中间,start会给你创建新线程,并自动调用run。我们在任何情况下都不需要自己手动调用run方法。
说到这里我们再来看一下API中的start方法:
public void start( )
使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
我们已经知道了如何按部就班的创建一个线程,但这并不能满足我们的好奇心,我们要直接将这两个类一探究竟,看看其中到底有什么方法和属性。Runnable类中没有属性,只有一个run方法。这个接口应该由那些打算通过某一线程执行其实例的类来实现。而Thread类却比较的复杂。
所以上例中Run类有一个理所当然的写法就是继承Thread类,因为Thread实现了Runnable,这两种写法实际上都是重写run方法。但是面试题非常喜欢问你:Thread和Runnable这两种实现多线程的方式哪种好?这时应该回答Runnable方法好的。原因也很简单,继承只能有一个,接口可以有很多,Runnable是接口不阻碍这个类继承的灵活性。
Thread的构造器有很多,参数主要有这四种:
public Thread(ThreadGroup group,Runnable target, String name,long stackSize)
其他的构造器都是这四种参数的组合,那么我们来看一下这四种参数的含义:
ThreadGroup是线程组的意思,它可以对一批线程进行分类管理。对线程组的控管理,即同时控制线程组里面的这一批线程。用户创建的所有线程都属于指定线程组,如果没有显示指定属于哪个线程组,那么该线程就属于默认线程组(即main线程组)。默认情况下,子线程和父线程处于同一个线程组。只有在创建线程时才能指定其所在的线程组,线程运行中途不能改变它所属的线程组,也就是说线程一旦指定所在的线程组,就直到该线程结束。线程组与线程之间结构类似于树形的结构,线程只能访问自己线程组的信息,不能访问父线程组或其他线程组内的信息。
target就是其run方法被调用的对象。如果这个参数是null,则这个Thread类中要自己重写run方法,否则这个Thread类的run方法是调用这个Runnable类中的run方法。
每个线程都有一个自己的名字,其实这个参数是很少被使用的,有的时候我们命名一个线程并不用这种形式。但是如果我们不去填这个参数的话,线程会自动的新生成一个名称。自动生成的名称的形式为 “Thread-“+n ,其中的 n 为整数。
这是一种具有平台依赖性的参数,stackSize能指定堆栈的大小。 在某些平台上,指定一个较高的 stackSize 参数值可能使线程在抛出 StackOverflowError 之前达到较大的递归深度。stackSize 参数的值与最大递归深度和并发程度之间的关系细节与平台有关。在某些平台上,stackSize 参数的值无论如何不会起任何作用。 作为建议,可以让虚拟机自由处理 stackSize 参数。
Thread中还有很多很多实用的方法,我们在涉及到具体概念的时候再给予介绍。
我们既然创建了线程,那么这个线程在什么时候终止呢?有两种情况:
当线程的run方法执行方法体中最后一条语句后,正常退出而自然死亡。
出现了在方法中没有捕获的异常,此时终止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六种,我们来逐个分析一下这几种状态的含义:
这里的创建新的线程真的是仅仅new了一个线程。
new Thread(r);
创建新的线程,是指刚刚new出来的线程,这个线程没有通过start的方法来启动。
那么一旦我们调用了start方法,这个线程开始工作。这是他就处于可运行状态,这个可运行状态不只是包含线程运行的时候,线程在中断的时候也被算为可运行状态。一个可运行状态的线程可能在运行也可能没在运行,我们不要因为一个线程在可运行的状态下没运行而急躁,很有可能这个线程的终止只是为了让其他的线程获得机会。
当一个线程试图去获得一个内部锁时,但这个内部锁被其他的线程持有,这个时候,为了等待去使用这个内部锁,这个线程将会暂时处在被阻塞的状态。当其他线程释放锁的时候,这个线程获得了内部锁,并且从阻塞状态转变为非阻塞状态。
当一个线程等待另一个线程通知调度器一个条件时,这个线程自己进入等待状态。等待状态和阻塞状态很类似,但是他们是存在本质区别的。如果另一个线程通知调度器结束,那么这个线程进行工作,等待状态也随之结束。
计时等待和等待是比较相似的,计时等待是表示他有一个超时参数。调用他们导致线程会进入计时等待。这个状态将一直保持到超市期满或者接收到适当的通知。相比较直接的等待,变得更加的安全。
线程的终止,我们在上面的线程中断中有所提及。线程终止理论上只有两种情况:当线程的run方法执行方法体中最后一条语句后,正常退出而自然死亡。2. 出现了在方法中没有捕获的异常,此时终止run方法,意外死亡。
线程中有很多的属性,尽管我在API中只看到一些其中一小部分的字段,但是线程的这种理念,在方法中也有所体现。线程的属性有以下这些:线程优先级,守护线程,处理未捕获异常的处理器。
在java中,每个线程都有一个优先级,我们需要知道的是,在没经过特殊处理的时候,所有的线程优先级都是一样的。默认的,我们把优先级分成1到10之间,高优先级的线程会先被操作。说到这里不由得让我们想起了操作系统中的进程优先级,和一些类似老化这样的名词。实际上,java虚拟机也确实是用进程的优先级来类比出线程的优先级,这样做最大的一个问题就在于,每个操作系统对于进程优先级的处理并不相同,java的线程优先级也因此而具有平台变化。实际上,我们不应该把程序的正确性依赖于线程优先级,我们应该尽量少的使用线程优先级。我们在这里介绍了线程优先级,但是我们并不建议去使用它。
守护线程是指通过:
t.setDaemon(ture); //将线程转换为守护线程
这样的写法,将一个线程转换为守护线程。守护线程的作用是为其他的线程提供服务,如果其他所有的线程都被退出,只剩下守护线程,那么程序也就结束了。没有去单独运行守护线程的必要。比如说其他线程的计时器,我们就可以将它设置为一个守护线程。
run方法不能抛出任何被检查,不被检测的异常会导致线程终止。在这种情况下,线程就死亡了。在这种情况下,我们需要实现一个Thread.UncaughtExceptionHandler。我们可以通过这种方式来知道,真正让我们的run意外死亡的问题在哪里。
在java的并发程序中,我们先了解了这些线程的状态和属性,下面我们就可以来研究关于线程之间同步的问题。如何能几个线程同时运行,让他们之间配合的好。
在操作系统中,我们通过分时的方法在CPU上不断地切换处理多个进程任务,给人并行处理的感觉,这种方法在操作系统中叫做多任务。多任务在较低层次上扩展出多线程的概念,也就是指一个程序同时执行多个线程。这种可以同时运行一个以上的线程的程序,我们叫做多线程程序。
以上就是java线程(一)—线程状态及属性详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!