Java中一個線程只有六個狀態。至於阻塞、可運行、掛起狀態都是人們為了便於理解,自己加上去的。


java中,線程的狀態使用一個枚舉類型來描述的。這個枚舉一共有6個值: NEW(新建)、RUNNABLE(運行)、BLOCKED(鎖池)、TIMED_WAITING(定時等待)、WAITING(等待)、TERMINATED(終止、結束)。

但是我發現大多數人的理解和上面的這六種還是有些差別,通常會加上阻塞狀態,可運行狀態,掛起狀態。

這是Thread類描述線程狀態的枚舉類的源代碼:

 public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

一、大多數人對線程狀態以及狀態轉換的理解

線程的狀態轉換:

  當一個線程創建以后,就處於新建狀態。那什么時候這個狀態會改變呢?只要它調用的start()方法,線程就進入了鎖池狀態

  進入鎖池以后就會參與鎖的競爭,當它獲得鎖以后還不能馬上運行,因為一個單核CPU在某一時刻,只能執行一個線程,所以他需要操作系統分配給它時間片,才能執行。所以人們通常把一個線程在獲得鎖后,獲得系統時間片之前的狀態稱之為可運行狀態,但Java源代碼里面並沒有可運行狀態這一說。

  當一個持有對象鎖的線程獲得CPU時間片以后,開始執行這個線程,此時叫做運行狀態

  當一個線程正常執行完,那么就進入終止(死亡)狀態。系統就會回收這個線程占用的資源。

  但是,線程的執行並不是那么順利的。一個正在運行的線程有可能會進入I/O交互,還可能調用sleep()方法,還有可能在當前線程當中有其它線程調用了join()方法。這時候線程就進入了阻塞狀態(但這也只是人們在理解的時候意淫加上去的,源代碼里也沒有定義這一個狀態)。阻塞狀態的線程是沒有釋放對象鎖的。當I/O交互完成,或sleep()方法完成,或其它調用join()方法的線程執行完畢。阻塞狀態的線程就會恢復到可運行狀態,此時如果再次獲得CPU時間片就會進入運行狀態。

  一個處於運行狀態的線程還可能調用wait()方法、或者帶時間參數的wait(long milli)方法。這時候線程就會將對象鎖釋放,進入等待隊列里面(如果是調用wait()方法則進入等待狀態,如果是調用帶時間參數的則進入定時等待狀態)

  一個線程如果的調用了帶時間參數的wait(long milli)方法進入了定時等待狀態,那么只要時間一到就會進入鎖池狀態,並不需要notify()或notifyAll()方法來喚醒它。如果調用的是不帶時間參數的wait()則需要notify()或notifyAll()這兩個方法來喚醒它然后進入鎖池狀態。進入鎖池狀態以后繼續參與鎖的競爭。

  當一個處於運行狀態的線程調用了suspend()方法以后,它就會進入掛起狀態(這一方法已經過時不建議使用)。掛起狀態的線程也沒有釋放對象鎖,它需要調用resume()方法以后才能恢復到可運行狀態。將線程掛起容易導致程序死鎖。

下面是我自己畫線程狀態轉換圖:

這是大多數對一個線程的狀態的理解。我以前也是這么理解的,但是,現在我對線程的狀態有了新的理解。這與大多數人的理解有些不一樣。

二、我對線程狀態以及轉換的理解

下面是我對線程的自己的理解,如果有不對的地方希望懂的人可以指出來一起討論一下。

據官方源碼,一個線程有六個狀態,沒有阻塞狀態,沒有可運行,沒有掛起狀態。

所以,現在我要提出一個觀點:一個處於等待狀態、定時等待狀態的線程它也可能持有對象鎖。例如調用sleep(long millis)會使線程進入等待狀態,但是沒有釋放鎖。

線程沒有阻塞狀態,那一個正在運行的線程進入I/O,或調用sleep()方法,或當前線程當中有其它線程調用了join()方法時,線程就會進入什么狀態呢,它總得有六個當中的一個吧。

①、當線程調用sleep()方法或當前線程中有其他線程調用了帶時間參數的join()方法的時候進入了定時等待狀態(TIMED_WAITING)。

 

②、當其他線程調用了不帶時間參數的join()方法時進入等待狀態(WAITING)。

 

③、當線程遇到I/O的時候還是運行狀態(RUNNABLE)。

④、當一個線程調用了suspend()方法掛起的時候它還是運行狀態(RUNNABLE)。

現在我要來證明一下以上四點,如果證明過程有誤,希望能夠得到指正。這些代碼的可以直接復制來運行一下。

證明一:當線程調用sleep()方法的時候進入了定時等待狀態。

現在兩個線程t1、t2,t1持有t2的一個引用。啟動兩個線程,t2啟動后立即睡眠,讓t1打印t2的狀態。這樣就可以看到睡眠時候的線程是六個當中的哪一個狀態了。

public class Test1
{
    public static void main(String[] args)
    {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
        t1.setThread2(t2);
        
        t1.start();
        t2.start();
    }
}

//Thread1負責打印兩個線程的狀態。
class Thread1 extends Thread
{
    private Thread2 t2;
    
    public void setThread2(Thread2 t2)
    {
        this.t2 = t2;
    }
    
    @Override
    public void run()
    {
        System.out.println("進入t1線程");
        for(int i = 0; i < 5; i++)
        {
            try
            {
                System.out.println("t1 的狀態: " + getState());
                System.out.println("t2 的狀態: " + t2.getState());
                System.out.println();
                
                //為了減少打印次數,所以t1每打印一次睡1秒
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                
            }
        }
    }
}

class Thread2 extends Thread
{
    @Override
    public void run()
    {
        System.out.println("進入t2線程,馬上進入睡眠");
        try
        {
            //睡眠5秒鍾。
            sleep(5000);
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("t2睡眠結束");
    }
}

上面的程序執行打印的結果

說明調用sleep()方法以后線程處於定時等待狀態(TIMED_WAITING)至於網上一直說的處於等待狀態的線程不持有對象鎖這種說法,我不知道這是官方給出的還是人們自己定義的。

 證明二:當其他線程調用了join()方法時進入等待狀態。

現在有三個線程t1 、t2 、t3。t1負責打印三個線程的狀態。t2線程持有t3線程的引用,當進入t2線程以后,立即啟動t3線程,並調用t3.join()方法。當t3加入后t2就是等待狀態。

public class Test1
{
    public static void main(String[] args)
    {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
        Thread3 t3 = new Thread3();
        
        //t1需要持有t2,t3的引用,以便打印他們的狀態。
        t1.setThread2(t2,t3);
        
        //t2需要持有t3的引用,以便t3能夠在t2執行時加入(調用join()方法)
        t2.setTh3(t3);
        
        t1.start();
        t2.start();
    }
}

//Thread1負責打印所有線程的狀態。
class Thread1 extends Thread
{
    private Thread2 t2;
    private Thread3 t3;
    
    public void setThread2(Thread2 t2, Thread3 t3)
    {
        this.t2 = t2;
        this.t3 = t3;
    }
    
    @Override
    public void run()
    {
        System.out.println("進入t1線程");
        for(int i = 0; i < 5; i++)
        {
            try
            {
                System.out.println("t1 的狀態: " + getState());
                System.out.println("t2 的狀態: " + t2.getState());
                System.out.println("t3 的狀態: " + t3.getState());
                System.out.println();
                
                //為了減少打印次數,所以t1每打印一次睡1秒
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                
            }
        }
    }
}


class Thread2 extends Thread
{
    private Thread3 t3;
    
    public void setTh3(Thread3 t3)
    {
        this.t3 = t3;
    }
    
    //當進入t2線程以后馬上啟動t3線程並調用join()方法。
    @Override
    public void run()
    {
        System.out.println("進入t2線程,t3准備加入(調用join()方法)");
        t3.start();
        try
        {
            t3.join();
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("t2執行結束");
    }
}
class Thread3 extends Thread
{
    @Override
    public void run()
    {
        System.out.println("進入t3線程,准備睡眠");
        
        //本來是想讓t3線程做加法運算的,奈何電腦算太快了,所以改為睡眠。因為睡眠不釋放鎖,所以效果一樣。
        try
        {
            sleep(5000);
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        
        System.out.println("t3線程結束");
    }
}

這是運行的打印結果,當t3加入后t2處於等待狀態

 根據結果看,說明當一個正在執行的線程在其他線程調用join()方法以后進入了等待狀態。

 

 證明三:當一個線程調用了suspend()方法的時候它還是運行狀態(RUNNABLE)。

package com.zcd.observe;

public class Test1
{
    public static void main(String[] args)
    {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
        
        //t1需要持有t2,以便打印狀態,和控制它恢復運行。
        t1.setThread2(t2);
        
        t1.start();
        t2.start();
    }
}

//Thread1負責打印所有線程的狀態。
class Thread1 extends Thread
{
    private Thread2 t2;
    
    public void setThread2(Thread2 t2)
    {
        this.t2 = t2;
    }
    
    @Override
    public void run()
    {
        System.out.println("進入t1線程");
        for(int i = 0; i < 6; i++)
        {
            try
            {
                System.out.println("t1 的狀態: " + getState());
                System.out.println("t2 的狀態: " + t2.getState());
                System.out.println();
                
                if(i == 3)
                {
                    //恢復t2的運行。
                    t2.resume();
                }
                
                //為了減少打印次數,所以t1每打印一次睡1秒
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                
            }
        }
        
    }
}

class Thread2 extends Thread
{
    @Override
    public void run()
    {
        System.out.println("進入t2線程,掛起");
        //將線程掛起。讓t1來控制它的恢復運行。
        suspend();
        
        System.out.println("t2已經恢復運行");
        System.out.println("t2正在打印1");
        System.out.println("t2正在打印2");
        System.out.println("t2正在打印3");
        
        
        System.out.println("t2線程結束");
    }
}

執行結果截圖

說明:當一個線程調用了suspend()方法的時候它還是運行狀態(RUNNABLE)。

證明四:當線程遇到I/O的時候還是運行狀態。

package com.zcd.observe;

import java.util.Scanner;

public class Test1
{
    public static void main(String[] args)
    {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
        
        t1.setThread2(t2);
        
        t1.start();
        t2.start();
    }
}

//Thread1負責打印所有線程的狀態。
class Thread1 extends Thread
{
    private Thread2 t2;
    
    public void setThread2(Thread2 t2)
    {
        this.t2 = t2;
    }
    
    @Override
    public void run()
    {
        System.out.println("進入t1線程");
        for(int i = 0; i < 6; i++)
        {
            try
            {
                System.out.println("t1 的狀態: " + getState());
                System.out.println("t2 的狀態: " + t2.getState());
                System.out.println();
                
                
                //為了減少打印次數,所以t1每打印一次睡1秒
                Thread.sleep(1000);
            } catch (InterruptedException e)
            {
                
            }
        }
        System.out.println("進入t1線程結束");
    }
}

class Thread2 extends Thread
{
    @Override
    public void run()
    {
        System.out.println("進入t2線程");
        
        //讓線程進入I/O
        System.out.println("請輸入數據:");
        Scanner scan = new Scanner(System.in);
        String read = scan.nextLine();
        System.out.println("您輸入的數據為:"+read); 
        
        System.out.println("t2線程結束");
    }
}

執行結果截圖:

說明:當線程遇到I/O的時候還是運行狀態。

 下面在附上我畫的狀態轉換圖,一共只有六個狀態。

 

 

   我覺得以上兩種理解方式只是站在同的角度去理解而已。

本文為作者原創,轉載需要聲明並且附上本文地址:http://www.cnblogs.com/GooPolaris/p/8079490.html 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM