您的位置:首页 > 其它

Thread、Runnable以及线程的生命周期

2015-11-21 14:54 260 查看
首先 Thread是类,Runable是接口。
线程的起动并不是简单的调用了你的RUN方法,而是由一个线程调度器来分别调用你的所有线程的RUN方法,
我们普通的RUN方法如果没有执行完是不会返回的,也就是会一直执行下去,这样RUN方法下面的方法就不可能会执行了,可是线程里的RUN方法却不一样,它只有一定的CPU时间,执行过后就给别的线程了,这样反复的把CPU的时间切来切去,因为切换的速度很快,所以我们就感觉是很多线程在同时运行一样.

你简单的调用run方法是没有这样效果的,所以你必须调用Thread类的start方法来启动你的线程.所以你启动线程有两种方法
一是写一个类继承自Thread类,然后重写里面的run方法,用start方法启动线程
二是写一个类实现Runnable接口,实现里面的run方法,用new Thread(Runnable target).start()方法来启动
这两种方法都必须实现RUN方法,这样线程起动的时候,线程管理器好去调用你的RUN方法.

查看源码可以发现Thread也是实现的Runable

public
class Thread implements Runnable {


两个都可以实现多线程编程,但是基于java是单继承,所以实现Runable更灵活。并且Runable可以简单的实现变量的线程间共享。

由于Runable接口中只定义了一个方法,即run(),之前提到多线程需要操作系统的支持,而run()方法是无法启动的,需要通过Thread类的start()启动操作系统对多线程的支持。在Thread类中提供了一个以Runabel类型参数的构造函数,因此另一个实现多线程的方法是先实现Runable接口,然后再通过Thread(Runable)构建一个线程,通过Thread中的start()方法间接启动操作系统对多线程的支持。

实现Runable 实现共享

static class MyThread2 implements Runnable{
private int ticket=10;
private String threadName;
public MyThread2( String threadName) {

this.threadName = threadName;
}
@Override
public void run() {
for(;ticket>0;ticket--){
System.out.println(threadName+"卖出火车票号:"+ticket);
}
}
}
public static void main(String[] args) {
//        MyThread1 thread1=new MyThread1("从线程1");
//        MyThread1 thread2=new MyThread1("从线程2");
//        thread1.start();
//        thread2.start();
MyThread2 thread3 = new MyThread2("Runable实现类");
new Thread(thread3).start();
new Thread(thread3).start();
}
执行结果:



虽然这样可以共享但是这样也存在数据不同步的现象:

两个线程可能读到的ticket不是同步的;



这就需要在实现类里加入同步的代码:

{
synchronized (this){
for (; ticket > 0; ticket--) {
System.out.println(threadName + "卖出火车票号:" + ticket);
}
}
}


认识Thread的start和run

1) start:

用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

2) run:

run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。

源码分析:

如下代码段 1:

[java] view
plaincopyprint?

new Thread(new Runnable() {  

    @Override  

    public void run() {  

        System.out.println("Run of Runnable");  

    }  

}) {  

    public void run() {  

        System.out.println("Run of Thread");  

    }  

}.start();  



执行上述代码段,到底会调用哪个run()方法呢?

再看如下代码段 2:

[java] view
plaincopyprint?

new Thread(new Runnable() {  

    @Override  

    public void run() {  

        System.out.println("Run of Runnable");  

    }  

}) {  

    public void run() {  

        System.out.println("Run of Thread");  

        super.run();  

    }  

}.start();  

执行上述代码段,又会调用哪个run()方法呢?

首先,我们来看一下JDK的Thread源码,片段 3 如下:

[java] view
plaincopyprint?

private Runnable target;  

[java] view
plaincopyprint?

public void run() {  

    if (target != null) {  

        target.run();  

    }  

}  

在run()方法中,首先会检查target是否为空,如果不是,则执行该target的run()方法。

那么,对于上面两段代码的执行,也就清楚了。

在第一个代码段 1 中,重写了Thread的run()方法,同时传入了一个Runnable对象,该对象也实现了run()方法。该Thread对象调用start()方法后,会执行该对象重写的run()方法,其输出结果也就是Run of Thread,输出完后,run()方法返回,该线程对象的生命周期也就结束了。

在第二个代码段 2 中,同样也重写了Thread的run()方法,同时传入了一个Runnable对象,实现了run()方法。唯一不同的是,在Thread重写的run方法中,在打印输出后,还执行了super.run(),这就有意思了。

首先,该线程启动运行后,执行其重写的run()方法,输出Run of Thread。

接下来调用super.run(),也就是调用超类的run()方法,而该超类的run()方法,也就是JDK定义的Thread类的run(),其执行如上代码段 3 所示;显然target不为空,这时候会调用该对象的run()方法,会输出Run of Runnable.。

如果,上面的Thread并未重写run()方法,那么,执行的结果还是一样。首先会执行该Thread的run()方法,因为此时并未重写该方法,所以还是会调用JDK定以的run()方法,也就是上面的代码段 3,在该代码段中会判断target是否为空,显然不是,所以会调用Runnable对象实现的run()方法。

总结:对于Thread(Runnable target ...),不管传入的Target是否为空,首先都会执行Thread自己的run()方法。如果重写了该方法且该方法中没有super.run(),那么是永远不会调用Runnable实现的run()方法;如果没有重写该方法,则会去判断target是否为空,以此来决定调用target实现的run()方法;如果重写了该方法,且该方法中有super.run(),在执行完该语句之前的所有代码后,会判断target是否为空,以此来决定调用target实现的run()方法,执行完后,接着执行该语句之后的代码。

run方法是怎么被调用的?

[java] view
plaincopy

   public synchronized void start() {  

       /** 

 * This method is not invoked for the main method thread or "system" 

 * group threads created/set up by the VM. Any new functionality added  

 * to this method in the future may have to also be added to the VM. 

 * 

 * A zero status value corresponds to state "NEW". 

        */  

       if (threadStatus != 0 || this != me)  

           throw new IllegalThreadStateException();  

       group.add(this);  

       start0();  

       if (stopBeforeStart) {  

    stop0(throwableFromStop);  

}  

   }  

  

   private native void start0();  

  

   public void run() {  

if (target != null) {  

    target.run();  

}  

   }  

这是Thread类的run()方法的代码。一目了然如果target存在的话执行target的run()方法,否则什么也不做。这样我们可以推测(因为Thread 实际运行的方法start0 是native方法 我们看不到它的实现)也就是说Thread的run()方法总是先被调用的,然后调用taget(构造函数中的Runnable对象)的run()方法。

线程的生命周期:

(1)生命周期的五种状态

   新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。

例如:Thread  t1=new Thread();

就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();

运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

死亡(dead)

当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

自然终止:正常运行run()方法后终止

异常终止:调用stop()方法让一个线程终止运行

堵塞(blocked)

由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

正在等待:调用wait()方法。(调用motify()方法回到就绪状态)

被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

线程将处于下列4种状态之一。

newly created:当我们new Thread()时,线程所处的状态就是新创建的状态(newly created),但是此时系统并不分配资源;给线程发送消息start()时,才分配资源。
runable:给线程发送start()消失时,进入runable状态。此时抢到cpu的线程开始执行run()方法。其他调用start()的线程在queue里排队等待cpu调度。
blocked:线程被挂起时,离开runable状态,知道结束回到runable状态。

a.线程内部调用sleep()时,休息,被挂起。时间到,则运作。

b.给线程发送消息wait()时,等待,被挂起。线程收到消息,notify();

c.某个线程调用join()时,其他线程会等到该线程结束后才会开始执行。会抛出InterruptedException异常,因此要写在try-catch块里。

dead:线程的run()方法执行结束or线程调用了stop()函数时,线程进入销毁状态。

线程状态说明

线程状态从大的方面来说,可归结为:初始状态、可运行状态、不可运行状态和消亡状态,具体可细分为上图所示7个状态,说明如下:

1) 线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样,当我们new了thread实例后,线程就进入了初始状态;

2) 当该对象调用了start()方法,就进入可运行状态;

3) 进入可运行状态后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;

4) 进入运行状态后case就比较多,大致有如下情形:

﹒run()方法或main()方法结束后,线程就进入终止状态;

﹒当线程调用了自身的sleep()方法或其他线程的join()方法,就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源)。当 sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配时间片;

﹒当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被锁牢(synchroniza,lock),将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可运行状态,等待OS分配 CPU时间片;

﹒当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。

﹒当线程调用stop方法,即可使线程进入消亡状态,但是由于stop方法是不安全的,不鼓励使用,大家可以通过run方法里的条件变通实现线程的 stop。


线程的优先级

  1.线程的优先级及设置

  线程的优先级是为了在多线程环境中便于系统对线程的调度,优先级高的线程将优先执行。

  一个线程的优先级设置遵从以下原则

  线程创建时,子继承父的优先级。

  线程创建后,可通过调用setPriority()方法改变优先级。

  线程的优先级是1-10之间的正整数。

  1- MIN_PRIORITY

  10-MAX_PRIORITY

  5-NORM_PRIORITY

  如果什么都没有设置,默认值是5。

  但是不能依靠线程的优先级来决定线程的执行顺序。

 

  2.线程的调度策略

  线程调度器选择优先级最高的线程运行。但是,如果发生以下情况,就会终止线程的运行:

  线程体中调用了yield()方法,让出了对CPU的占用权。

  线程体中调用了sleep()方法,使线程进入睡眠状态。

  线程由于I/O操作而受阻塞。

  另一个更高优先级的线程出现。

  在支持时间片的系统中,该线程的时间片用完。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: