java多线程(三)、线程的生命周期(状态)和线程的操作

三、线程的生命周期(状态)和线程操作

(一)线程的生命周期(状态)

 

                                                                                                                                线程的生命周期(状态转移)图

新建:当程序使用new创建一个线程后(用new关键字和Thread类或其子类建立一个线程对象)该线程处于新建状态,此时他和其他java对象一样,仅仅由Java虚拟机为其分配内存并初始化成员变量值。Thread r = new Thread() 

就绪:当线程对象调用start()方法后,该线程处于就绪状态,线程计入线程队列排队,此时该状态线程并未开始执行,它仅表示可以运行了。至于该线程何时运行,取决于JVM线程调度器的调度。r.start() 

运行:处于就绪状态的线程获得了CPU,开始执行run()线程执行体,该线程处于执行状态。如果该线程失去了CPU资源,就会又从运行状态变为就绪状态。从图示可以看出,它可以变为阻塞状态、就绪状态和死亡状态。

阻塞:线程运行过程中需要被中断,目的是是其他的线程获得执行的机会。该状态就会进入阻塞状态。思考一下:什么情况下线程会从运行状态变为阻塞状态??当发生如下情况是,线程会从运行状态变为阻塞状态:@Lonely Roamer

①、线程调用sleep方法主动放弃所占用的系统资源

②、线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

③、线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有

④、线程在等待某个通知(notify

⑤、程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法

注意:阻塞状态不能直接转成运行状态,阻塞状态只能重新进入就绪状态

死亡:线程调用stop()方法时或run()方法执行结束后,即处于死亡状态。处于死亡状态的线程不具备有继续执行允许的能力。

注意:在调用stop()方法结束线程易导致死锁,不推荐。同时,主线程结束后,其他线程不受其影响,不会随之结束。一旦子线程启动起来后,就拥有和主线程相等地位,不受主线程影响。

 

 

(二)、线程的操作

如下Thread类得主要方法:(注意一点,在java实现多线程的程序中,虽然Thread的类实现了Runnable接口但是操作线程的主要方法并不在Runnable接口中,而是在Thread类中,下表就是Thread的类中的主要方法。)

 

下面就将详细介绍常用的方法。方法摘要如下 

1,static Thread currentThread() 

          返回对当前正在执行的线程对象的引用。

2,long getId() 

          返回该线程的标识符。 

3,String getName() 

          返回该线程的名称。 

    void setName(String name) 

          改变线程名称,使之与参数 name 相同。

4,int getPriority() 

          返回线程的优先级。 

5,void interrupt() 

          中断线程。 

6,boolean isAlive() 

          测试线程是否处于活动状态。 

7,boolean isDaemon() 

          测试该线程是否为守护线程。  

8,void join() 

          等待该线程终止。 

   void join(long millis) 

          等待该线程终止的时间最长为 millis 毫秒。 

   void join(long millis, int nanos) 

          等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。  

   void run() 

          如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 

9,void setDaemon(boolean on) 

          将该线程标记为守护线程或用户线程。   

10,void setPriority(int newPriority) 

          更改线程的优先级。 

11,static void sleep(long millis) 

          在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 

12,static void sleep(long millis, int nanos) 

          在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 

13,void start() 

          使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 

14,void stop() 

          已过时。 该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。 

15,void stop(Throwable obj) 

          已过时。 该方法具有固有的不安全性。有关详细信息,请参阅 stop()。该方法的附加危险是它可用于生成目标线程未准备处理的异常(包括若没有该方法该线程不太可能抛出的已检查的异常)。有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。 

16,void suspend() 

          已过时。 该方法已经遭到反对,因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用 resume 之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。  

17,static void yield() 

          暂停当前正在执行的线程对象,并执行其他线程。 

下面重点介绍void start()boolean isAlive() void join() setPriority/int getPriority() static void sleep()void interrupt() void setDaemon(boolean on) static void yield() 等方法。

1、void start()方法。

       创建对象,调用方法。类如下示代码片段。

MyThread t = new MyThread();

new Thread(t).start();

2boolean isAlive()

      测试线程是否活着,可用线程对象的isAlive()方法。当线程处于就绪,运行,阻塞状态返回true。当线程处于新建和死亡状态,返回false

看下面一个例子:

class mythread implements Runnable{     /*实现Runnable接口

     Public void run(){                 /*覆写run()方法

        ...... xxx方法体xxx ......

     }

 

};

public class isAliveMethod{

     public static void main(String args[] ){

     Thread p = new Thread (new mythread(),"mythread线程");

     System.out.println(“start()方法调用之前-->”+p.isAlive());//判断线程是否启动

     p.start();      //启动线程

     System.out.println(“start()方法调用之后-->”+p.isAlive());//判断线程是否启动

 

     }

}

运行结果:

start()方法调用之前-->false

start()方法调用之后-->true

3、void join()

      也叫联合线程。调用join方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,必须等到该线程结束后其他线程才可以运行。应用场景是当一个线程必须等待另一个线程执行完毕才能执行时。三个方法,很少用第三个!

   void join()  等待该线程终止。 

   void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 

   void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

 

范例如下:

public class JoinDemo {

public static void main(String[] args) throws Exception {

Thread myjoin = new Thread(new Join(),"Join线程");

myjoin.start();

int i = 0;

while(i < 500){

if(i == 100){

  myjoin.join();  //在主线程中调用myjoin.join(); 就是将主线程加入到myjoin子线程后面等待执行。不过有时间限制

}

System.out.println("主 -- > " + “线程第”+ i++ +“次执行””);

}

}

}

 

class Join implements Runnable {

public void run() {

int i = 0;

while(i< 200){

System.out.println(Thread.currentThread().getName() + "-->" + “线程第”+ i++ +“次执行” );

}

}

}

 

4、static void sleep()

       static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

即:在调用sleep()后,会使执行的线程暂停一段时间,进入阻塞状态,在此指定的时间段之内,该线程不会获得执行的机会。

参看如下分析:本部分内容参考自@LonelyRoamer

示例:

public class Test1 {  

    public static void main(String[] args) throws InterruptedException {  

        for(int i=0;i<100;i++){  

            System.out.println("main"+i);  

            Thread.sleep(100);  

        }  

    }  

}  

可以明显看到打印的数字在时间上有些许的间隔。

 

注意如下几点问题

sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。看下面的例子:

view plaincopy

public class Test1 {  

    public static void main(String[] args) throws InterruptedException {  

        System.out.println(Thread.currentThread().getName());  

        MyThread myThread=new MyThread();  

        myThread.start();  

        myThread.sleep(1000);//这里sleep的就是main线程,而非myThread线程  

        Thread.sleep(10);  

        for(int i=0;i<100;i++){  

            System.out.println("main"+i);  

        }  

    }  

}  

 

Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。

public class Test1 {  

    public static void main(String[] args) throws InterruptedException {  

        new MyThread().start();  

        new MyThread().start();  

    }  

}  

  

class MyThread extends Thread {  

    @Override  

    public void run() {  

        for (int i = 0; i < 3; i++) {  

            System.out.println(this.getName()+"线程" + i + "次执行!");  

            try {  

                Thread.sleep(50);  

            } catch (InterruptedException e) {  

                e.printStackTrace();  

            }  

        }  

    }  

}  

看某一次的运行结果:

Thread-0线程0次执行!  

Thread-1线程0次执行!  

Thread-1线程1次执行!  

Thread-0线程1次执行!  

Thread-0线程2次执行!  

Thread-1线程2次执行!  

可以看到,线程0首先执行,然后线程1执行一次,又了执行一次。可以看到它并不是按照sleep的顺序执行的。

 

5int setPriority() /int getPriority()

      每个线程都有优先级,优先级的高低只和线程获得执行机会的次数有关。这里需要指明,并非线程优先级越高的就一定先执行,到底哪个线程先运行还是要取决于CPU的调度。不过优先级高的被CPU调度执行的机会次数宏观上要多于优先级低的线程。

    默认情况下main线程具有普通的优先级,而它创建的线程也具有普通的优先级。Thread类提供了setPriority(int newPriority)getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法可以设置一个线程的优先级,在java中一般有三种优先级。如下所以:

public static final int MAX_PRIORITY   =10

public static final int MIN_PRIORITY   =1

public static final int NORM_PRIORITY   =5

实例演示如下:

class MyThread implements Runnable {

    public void run() {               //覆写Runnable方法

  for (int i = 0; i <5; i++) {

try{ 

              Thread.sleep(500);     //线程休眠

              }catch (Exception e) {}  //需要异常处理

         System.out.println(Thread.currentThread().getName() + "运行,i" + i);

         // currentThread():返回对当前正在执行的线程对象的引用。

         // getName():返回线程名称。  

}

}

}

public class ThreadPriorityDemo{

public static void main(String[] args){

Thread t1 = new Thread(new MyThread(),”线程 A”)//实例化线程对象

Thread t2 = new Thread(new MyThread(),”线程 B”)

Thread t3 = new Thread(new MyThread(),”线程 C”)

t1.setPriority(Thread.MIN_PRIORITY);          //设置线程优先级

t2.setPriority(Thread.MAX_PRIORITY);         

t3.setPriority(Thread.NORM_PRIORITY);   

t1.start();                                     //启动线程

t2.start();

t3.start()’       

}

   }

程序运行结果(理想情况下)

线程B运行,i=0       说明:第二个线程(t2)的优先级:高

线程C运行,i=0       说明:第二个线程(t3)的优先级:中

线程A运行,i=0       说明:第二个线程(t1)的优先级:低

线程B运行,i=1       

线程C运行,i=1      

线程A运行,i=1

线程B运行,i=2       

线程C运行,i=2      

线程A运行,i=2

线程B运行,i=3       

线程C运行,i=3      

线程A运行,i=3

线程B运行,i=4       

线程C运行,i=4      

线程A运行,i=4

 

从程序运行结果来看,线程将根据其优先级的大小来决定哪个线程先运行。但是

一定要注意的是,并非线程的优先级越高就一定会先执行。上述实验结果出现的

概率是很小的,你的实验结果将会绝大部分的按优先级高低来输出,但并不总是。

6static void yield()

      线程礼让:暂停当前正在执行的线程对象,并执行其他线程;

说明:Thread的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。所以完全有可能:某个线程调用了yield()之后,线程调度器又把他调度出来重新执行。

例子如下:

class MyThread implements Runnable {

   public void run() {               //覆写Runnable方法

  for (int i = 0; i <5; i++) {

         System.out.println(Thread.currentThread().getName() + "运行,i" + i);

                          //输出线程的名称

             Ifi==3{

                      System.out.println(“线程礼让:”)

                      Thread.currentThread.yied() ;   //线程礼让

              }   

}

}

};

public class ThreadYieldDemo{

public static void main(String[] args){

MyThread my= new MyThread()//实例化MyThread对象

Thread t1 = new Thread(my,”线程 A”)//定义线程对象

Thread t2 = new Thread(my,”线程B”)

t1.start();                                     //启动多线程

t2.start();

       

}

   }

我们可以冲程序的运行结果中发现,每当线程满足条件(i=3)时,就会将本线程暂停,而让其他线程执行。注意sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,所以很有可能刚进入就绪状态,又被调度到运行状态。

7、void setDaemon(boolean on)

      在介绍此方法之前有必要先介绍一下后台线程

所谓后台线程:处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。JVM的垃圾回收就是典型的后台线程。

特点

线程优先级特别低,只有在同一程序中的其他线程不执行时才会执行。

若所有的前台线程都死亡,后台线程自动死亡。设置后台线程:Thread对象setDaemon(true);

 

注意

setDaemon(true)必须需在start()调用前,否则出现IllegalThreadStateException异常。前台线程创建的线程默认是前台线程,并且当且仅当创建线程是后台线程时,新线程才是后台线程。

判断是否是后台线程:使用Thread对象的isDaemon()方法。

用途

守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。

下述代码参考网上,便于对守护线程的理解:

此程序在启动后,守护线程一直在运行,当用户从键盘输入字符并按回车后,main线程结束,守护线程自动结束 

public class DaemonThread extends Thread {   

   public void run() {  //线程的run方法,它将和其他线程同时运行  

            while (true) {   

            for (int i = 1; i <= 100; i++) {   

                this.setSleep(200);   

                System.out.println(i);   

            }   

            this.setSleep(2000);   

        }   

    }   

       

    public void setSleep(long time) {   

        try {   

            Thread.sleep(time);   

        } catch (InterruptedException ex) {   

            ex.printStackTrace();   

        }   

    }   

}  

public class DarmonApp {   

    public static void main(String[] args) {   

        DaemonThread daemonThread = new DaemonThread();   

        // 设置为守护线程   

        daemonThread.setDaemon(true);   

        daemonThread.start();   

        System.out.println("isDaemon = " + daemonThread.isDaemon());   

        try {   

            System.in.read(); // 接受输入,使程序在此停顿,一旦接收到用户输入,main线程结束,守护线程自动结束   

        } catch (IOException ex) {   

            ex.printStackTrace();   

        }   

    }   

}  

 


8、void interrupt()

      Thread.interrupt()中断线程

说明:该方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.joinThread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

    因此,如果线程被上述几种方法阻塞,正确的停止线程方式是设置共享变量,并调用interrupt()(注意变量应该先设置)。如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。在任何一种情况中,最后线程都将检查共享变量然后再停止。

 

参考:http://shmilyaw-hotmail-com.iteye.com/blog/1881222

初步理解

    我们在看一些多线程代码的时候,有的时候会碰到使用interrupt()方法的时候。从字面的意思来理解,应该就是中断当前正在执行的线程。那么,对于一个我们设计的普通线程来说,如果我们在主线程里调用它的interrupt()方法,会不会导致它被中断呢?

    比如说我们先写一段如下的代码:  

public class ThreadCon extends Thread  

{  

    public void run()  

    {  

         

        for(int i = 0; i < Integer.MAX_VALUE; i++)  

        {  

            System.out.println(i);  

        }  

          

    }  

  

    public static void main(String[] args)  

    {  

        ThreadCon thread = new ThreadCon();  

        thread.start();  

        System.out.println("main thread");  

        try   

        {  

            TimeUnit.SECONDS.sleep(2);  

        }   

        catch(InterruptedException e)   

        {  

            e.printStackTrace();  

        }  

        thread.interrupt();  

    }  

}  

    这是一段比较简单的示例,我们在开始的时候启动一个子线程,这个线程打印从0Integer.MAX_VALUE 的值。而主线程先sleep 2秒钟。然后再尝试去中断子线程。

    如果我们去运行前面这一段代码,会发现子线程会一直在输出数字结果,它根本就不会停下来。部分结果的输出如下:

Java代码  收藏代码

141318  

141319  

141320  

141321  

141322  

141323  

141324  

141325  

     由此可见,我们对一个子线程发interrupt的消息时,如果线程是在运行的状态之下,它会忽略这个请求而继续执行的。

    那么,在什么情况下才会导致这个触发的interrupt消息让子线程能够响应呢?

interrupt方法处理方式

    interrupt方法调用的时候会设置该线程的interrupt标识位(flag),相当于设置了一个状态信息。执行的子线程需要检查该标识位是否被设置了然后才能做后续的执行步骤。这个检查的方法就是isInterrupted()。这样,在一旦检测到这个位被设置之后,我们就可以采取对应的措施。

    我们在前面的代码的基础上做一点修改:  

public class ThreadCon extends Thread  

{  

    public void run()  

    {  

         

        for(int i = 0; i < Integer.MAX_VALUE; i++)  

        {  

            System.out.println(i);  

            if(isInterrupted())  

            {  

                System.out.println("Interrupted...");  

                return;  

            }  

        }  

          

    }  

  

    public static void main(String[] args)  

    {  

        ThreadCon thread = new ThreadCon();  

        thread.start();  

        System.out.println("main thread");  

        try   

        {  

            TimeUnit.SECONDS.sleep(2);  

        }   

        catch(InterruptedException e)   

        {  

            e.printStackTrace();  

        }  

        thread.interrupt();  

    }  

}  

     这里,我们在run方法的循环里增加了一个判断,调用isInterrupted方法。如果被设置为interrupted,则显示一个信息然后退出。这个时候如果我们再执行这部分代码,会发现结果如下:

Java代码  收藏代码

141370  

141371  

141372  

141373  

141374  

141375  

141376  

141377  

141378  

Interrupted...  

     所以说,从前面的代码中可以看到,这是一种处理interrupt方法的手段。在实际的一些应用中,我们可以通过抛出异常然后由调用程序捕捉的方式来处理。比如在前面的这个示例里,我们判断了isInterrupted了之后就在接着的部分处理了。如果我们希望由调用该方法的使用者来处理。可以在这里不做进一步的处理而是直接写一个throw new InterruptedException()

这里也有一个稍微复杂点的示例,通过遍历文件系统,然后在处理的子方法里面来抛出异常,再由调用的方法来处理。

public class FileSearch implements Runnable {  

    private String initPath;  

    private String fileName;  

      

    public FileSearch(String initPath, String fileName) {  

            this.initPath = initPath;  

            this.fileName = fileName;  

    }  

      

    @Override  

    public void run() {  

        File file = new File(initPath);  

        if(file.isDirectory()) {  

            try {  

                directoryProcess(file);  

            } catch(InterruptedException e) {  

                System.out.printf("%s: The search has been interrupted",   

                        Thread.currentThread().getName());  

            }  

        }  

    }  

      

    private void directoryProcess(File file) throws InterruptedException {  

        File[] list = file.listFiles();  

        if(list != null) {  

            for(int i = 0; i < list.length; i++) {  

                if(list[i].isDirectory()) {  

                    directoryProcess(list[i]);  

                } else {  

                    fileProcess(list[i]);  

                }  

            }  

        }  

        if(Thread.interrupted()) {  

            throw new InterruptedException();  

        }  

    }  

      

    private void fileProcess(File file) throws InterruptedException {  

        if(file.getName().equals(fileName)) {  

            System.out.printf("%s : %s\n", Thread.currentThread().getName(),  

                    file.getAbsolutePath());  

        }  

        if(Thread.interrupted()) {  

            throw new InterruptedException();  

        }  

    }  

}  

 启用该线程的部分代码如下:

/** 

 *  Main class of the example. Search for the autoexect.bat file 

 *  on the Windows root folder and its subfolders during ten seconds 

 *  and then, interrupts the Thread 

 */  

public class Main {  

  

    /** 

     * Main method of the core. Search for the autoexect.bat file 

     * on the Windows root folder and its subfolders during ten seconds 

     * and then, interrupts the Thread 

     * @param args 

     */  

    public static void main(String[] args) {  

        // Creates the Runnable object and the Thread to run it  

        FileSearch searcher=new FileSearch("C:\\","autoexec.bat");  

        Thread thread=new Thread(searcher);  

          

        // Starts the Thread  

        thread.start();  

          

        // Wait for ten seconds  

        try {  

            TimeUnit.SECONDS.sleep(10);  

        } catch (InterruptedException e) {  

            e.printStackTrace();  

        }  

          

        // Interrupts the thread  

        thread.interrupt();  

    }  

  

}  

 

总结

       interrupt方法的效果是它会设置线程的状态位,但是对于正在运行状态的线程并不会导致它被立即中断。只有执行的线程在检测到相关信息后采取对应措施。我们常用的措施一般是调用isInterrupted方法或者捕捉被调用方法抛出的InterruptedException来做处理。整体的应用并不复杂。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值