Java Thread線程詳解

目錄

一、介紹

二、線程生命週期(狀態)

2.1、新建狀態(NEW):

2.2、就緒狀態(RUNNABLE):

2.3、阻塞狀態(BLOCKED):

2.4、線程死亡(DEAD):

三、終止線程4種方式:

四、sleep與wait區別

五、start與run區別

六、java後臺線程:

七、ExecutorServiceǃCallable、Future有返回值線程

八、線程同步

九、線程通信


一、介紹

  •  線程:線程是CPU調度的基本單位,也就是說在一個進程中可以有多個併發程序執行流,線程拓展了進程的概念,使得任務的執行得到更加的細分。線程是進程的執行單元,但是線程不是分配系統資源的單位,它們共享所在進程的資源,包括共享內存,公有數據,全局變量,進程文件描述符,進程處理器,進程代碼段,進程用戶ID等等。
  • 線程獨立擁有自己的線程ID,堆棧,程序計數器,局部變量,寄存器組值,優先級,信號屏蔽碼,錯誤返回碼等等,線程是獨立運行的,其執行是搶佔式的。線程共享進程資源,線程之間的通信要進程之間的通信來得容易得多。此外,線程的創建和銷燬的開銷也遠遠小於進程的系統開銷。

  • Thread類本質上是實現了Runnable接口的一個實例,代表一個線程的實例。啓動線程的唯一方 法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啓動一個新線 程,並執行run()方法。

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */

    /**
     * The synchronization object responsible for this thread's join/sleep/park operations.
     */
    private final Object lock = new Object();

    private volatile long nativePeer;

    boolean started = false;

    private volatile String name;

    private int         priority;
    private Thread      threadQ;
    private long        eetop;

    /* Whether or not to single_step this thread. */
    private boolean     single_step;

    /* Whether or not the thread is a daemon thread. */
    private boolean     daemon = false;

    /* JVM state */
    private boolean     stillborn = false;

    /* What will be run. */
    private Runnable target;

    /* The group of this thread */
    private ThreadGroup group;

    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;

    /* The inherited AccessControlContext of this thread */
    private AccessControlContext inheritedAccessControlContext;

    /* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    /*
     * The requested stack size for this thread, or 0 if the creator did
     * not specify a stack size.  It is up to the VM to do whatever it
     * likes with this number; some VMs will ignore it.
     */
    private long stackSize;

    /*
     * JVM-private state that persists after native thread termination.
     */
    private long nativeParkEventPointer;

    /*
     * Thread ID
     */
    private long tid;

    /* For generating thread ID */
    private static long threadSeqNumber;

    /* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     */

    private volatile int threadStatus = 0;


    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }

    /**
     * The argument supplied to the current call to
     * java.util.concurrent.locks.LockSupport.park.
     * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker
     * Accessed using java.util.concurrent.locks.LockSupport.getBlocker
     */
    volatile Object parkBlocker;

    /* The object in which this thread is blocked in an interruptible I/O
     * operation, if any.  The blocker's interrupt method should be invoked
     * after setting this thread's interrupt status.
     */
    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();
.
.
.}
  • 自定義線程

public class MyThread extends Thread {    
 public void run() {      
    System.out.println("MyThread.run()");     
    }   
}   
MyThread myThread1 = new MyThread();   
myThread1.start(); 


如果自己的類已經extends另一個類,就無法直接extends Thread,此時,可以實現一個 Runnable接口。 public class MyThread extends OtherClass implements Runnable {     
    public void run() {      
    System.out.println("MyThread.run()");     
    }   
} 


//啓動MyThread,需要首先實例化一個Thread,並傳入自己的MyThread實例: 
MyThread myThread = new MyThread();   
Thread thread = new Thread(myThread);   
thread.start();   //事實上,當傳入一個Runnable target參數給Thread後,Thread的run()方法就會調用 target.run() 

public void run() {     
    if (target != null) {      
        target.run();     
    }   
} 

二、線程生命週期(狀態)

當線程被創建並啓動以後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。 在線程的生命週期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞 (Blocked)和死亡(Dead)5種狀態。尤其是當線程啓動以後,它不可能一直"霸佔"着CPU獨自 運行,所以CPU需要在多條線程之間切換,於是線程狀態也會多次在運行、阻塞之間切換 

2.1、新建狀態(NEW):

          當程序使用new關鍵字創建了一個線程之後,該線程就處於新建狀態,此時僅由JVM爲其分配 內存,並初始化其成員變量的值 

2.2、就緒狀態(RUNNABLE):

        如果處於就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處於運行狀 態

2.3、阻塞狀態(BLOCKED):

        阻塞狀態是指線程因爲某種原因放棄了cpu 使用權,也即讓出了cpu timeslice,暫時停止運行。 直到線程進入可運行(runnable)狀態,纔有機會再次獲得cpu timeslice 轉到運行(running)狀 態。阻塞的情況分三種

  • 等待阻塞(o.wait->等待隊列):

運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue) 中。 

  • 同步阻塞(lock->鎖池):

運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線 程放入鎖池(lock pool)中。 

  • 其他阻塞(sleep/join):

運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時, JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O 處理完畢時,線程重新轉入可運行(runnable)狀態。 

2.4、線程死亡(DEAD):

    線程會以下面三種方式結束,結束後就是死亡狀態。 

  1. 正常結束:run()或call()方法執行完成,線程正常結束
  2. 異常結束:線程拋出一個未捕獲的Exception或Error
  3. 調用stop:直接調用該線程的stop()方法來結束該線程—該方法通常容易導致死鎖,不推薦使用

三、終止線程4種方式:

  • 正常運行結束

          程序運行結束,線程自動結束

  • 程序使用標誌主動退出線程

        一般 run()方法執行完,線程就會正常結束,然而,常常有些線程是伺服線程。它們需要長時間的 運行,只有在外部某些條件滿足的情況下,才能關閉這些線程。使用一個變量來控制循環,例如: 直接的方法就是設一個boolean類型的標誌,並通過設置這個標誌爲true或false來控制while 循環是否退出,代碼示例

public class ThreadSafe extends Thread { 
    public volatile boolean exit = false;  
        public void run() {  
        while (!exit){ 
            //do something 
        } 
    }  
} 

定義了一個退出標誌exit,當exit爲true時,while循環退出,exit的默認值爲false.在定義exit 時,使用了一個 Java 關鍵字 volatile,這個關鍵字的目的是使 exit 同步,也就是說在同一時刻只 能由一個線程來修改exit的值。

  • Interrupt方法結束

         使用interrupt()方法來中斷線程有兩種情況: 

  1.    線程處於阻塞狀態:如使用了 sleep,同步鎖的 wait,socket 中的 receiver,accept 等方法時, 會使線程處於阻塞狀態。當調用線程的 interrupt()方法時,會拋出 InterruptException 異常。 阻塞中的那個方法拋出這個異常,通過代碼捕獲該異常,然後 break 跳出循環狀態,從而讓 我們有機會結束這個線程的執行。通常很多人認爲只要調用 interrupt 方法線程就會結束,實 際上是錯的, 一定要先捕獲InterruptedException異常之後通過break來跳出循環,才能正 常結束run方法。 
  2. 線程未處於阻塞狀態:使用isInterrupted()判斷線程的中斷標誌來退出循環。當使用 interrupt()方法時,中斷標誌就會置true,和使用自定義的標誌來控制循環是一樣的道理。   
public class ThreadSafe extends Thread {     
    public void run() {          
    while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標誌來退出             
        try{ 
                Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出 
            }catch(InterruptedException e){ 
                e.printStackTrace(); 
                break;//捕獲到異常之後,執行break跳出循環 
            } 
        } 
    }  
} 
  • stop方法終止線程:

        程序中可以直接使用thread.stop()來強行終止線程,但是stop方法是很危險的,就象突然關 閉計算機電源,而不是按正常程序關機一樣,可能會產生不可預料的結果,不安全主要是: thread.stop()調用之後,創建子線程的線程就會拋出 ThreadDeatherror 的錯誤,並且會釋放子 線程所持有的所有鎖。一般任何進行加鎖的代碼塊,都是爲了保護數據的一致性,如果在調用 thread.stop()後導致了該線程所持有的所有鎖的突然釋放(不可控制),那麼被保護數據就有可能呈 現不一致性,其他線程在使用這些被破壞的數據時,有可能導致一些很奇怪的應用程序錯誤。因 此,並不推薦使用stop方法來終止線程。 
 

四、sleep與wait區別

1. 對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於 Object類中的。 
2. sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然 保持者,當指定的時間到了又會自動恢復運行狀態。

3. 在調用sleep()方法的過程中,線程不會釋放對象鎖。

4. 而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此 對象調用notify()方法後本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態。 

五、start與run區別

1. start()方法來啓動線程,真正實現了多線程運行。這時無需等待 run 方法體代碼執行完畢, 可以直接繼續執行下面的代碼。 2. 通過調用 Thread 類的 start()方法來啓動一個線程, 這時此線程是處於就緒狀態, 並沒有運 行。

3. 方法 run()稱爲線程體,它包含了要執行的這個線程的內容,線程就進入了運行狀態,開始運 行run函數當中的代碼。 Run方法運行結束, 此線程終止。然後CPU再調度其它線程。 

六、java後臺線程:

1. 定義:守護線程--也稱“服務線程”,他是後臺線程,它有一個特性,即爲用戶線程 提供 公 共服務,在沒有用戶線程可服務時會自動離開。

2. 優先級:守護線程的優先級比較低,用於爲系統中的其它對象和線程提供服務。

3. 設置:通過 setDaemon(true)來設置線程爲“守護線程”;將一個用戶線程設置爲守護線程 的方式是在 線程對象創建 之前 用線程對象的setDaemon方法。

4. 在Daemon線程中產生的新線程也是Daemon的。

5. 線程則是 JVM 級別的,以 Tomcat 爲例,如果你在 Web 應用中啓動一個線程,這個線程的 生命週期並不會和 Web 應用程序保持同步。也就是說,即使你停止了 Web 應用,這個線程 依舊是活躍的。

6. example: 垃圾回收線程就是一個經典的守護線程,當我們的程序中不再有任何運行的Thread, 程序就不會再產生垃圾,垃圾回收器也就無事可做,所以當垃圾回收線程是 JVM 上僅剩的線 程時,垃圾回收線程會自動離開。它始終在低級別的狀態中運行,用於實時監控和管理系統 中的可回收資源。

7. 生命週期:守護進程(Daemon)是運行在後臺的一種特殊進程。它獨立於控制終端並且周 期性地執行某種任務或等待處理某些發生的事件。也就是說守護線程不依賴於終端,但是依 賴於系統,與系統“同生共死”。當 JVM 中所有的線程都是守護線程的時候,JVM 就可以退 出了;如果還有一個或以上的非守護線程則JVM不會退出。 

8.yield():線程讓步,也是Thread的靜態方法,使得正在執行的線程暫停,但不會阻塞線程,只是交出CPU的控制權,將線程轉爲就緒狀態,讓系統調度器重新調度一次。當某個線程調用yield方法暫停後,只有優先級與當前線程相同,或者優先級比當前線程更高的線程纔有可能獲得執行機會。

9.改變線程優先級:setPriority(int newPriority),高優先級的線程能獲得更多的執行機會。

七、ExecutorServiceǃCallable<Class>、Future有返回值線程

有返回值的任務必須實現Callable接口,類似的,無返回值的任務必須Runnable接口。執行 Callable任務後,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務 返回的Object了,再結合線程池接口ExecutorService就可以實現傳說中有返回結果的多線程 了。 
 

//創建一個線程池    
ExecutorService pool = Executors.newFixedThreadPool(taskSize);    
// 創建多個有返回值的任務    
List<Future> list = new ArrayList<Future>();    
for (int i = 0; i < taskSize; i++) {    
    Callable c = new MyCallable(i + " ");    
    // 執行任務並獲取Future對象    
    Future f = pool.submit(c);     
    list.add(f);    
}    

// 關閉線程池    
pool.shutdown();     
// 獲取所有併發任務的運行結果    
for (Future f : list) {    
    // 從Future對象上獲取任務的返回值,並輸出到控制檯    
    System.out.println("res:" + f.get().toString());    
}  
 

八、線程同步

  • 線程的同步的意義在於線程安全,也就是說有多個線程併發訪問同一個對象,而線程調度的不確定性可能帶來潛在的安全問題。

  • 同步監視器:java多線程引入同步監視器來解決同步問題,任何時刻只能有一個線程獲得對同步監視器的鎖定,當同步代碼塊執行完成後,該線程會釋放對同步監視器的鎖定。java允許任何對象作爲同步監視器,通常我們使用可能被併發訪問的共享資源作爲同步監視器。

  • 同步代碼塊:顯式指定同步監視器

public class MyThread extends Thread {
	private Account account;

	
	public MyThread(String name,Account account,double drawaccount) {
		super(name);
		this.account = account;
	}
	
	public void run() {
		synchronized(account) {
			if(account.getBlance()>=drawaccount) {
				System.out.println(getName());
				try {
					Thread.sleep(1);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
  • 同步方法:隱式指定同步監視器,使用synchronized關鍵字來修飾某個方法,此時該方法(非static方法)無需顯式指定同步監視器,同步監視器默認爲this,也就是調用該方法的對象
	public synchronized void draw(double amount) {
		……
	}
  • 同步監視器的釋放:線程會在以下幾種情況下釋放同步監視器的鎖定。
    1. 當前線程的同步方法,同步代碼塊執行結束或者在執行中遇到break,return等終止了代碼塊的執行
    2. 同步代碼塊或者方法中出現未處理的Error或者Exception,導致異常結束
    3. **當前線程執行同步代碼塊或者同步方法時,程序中執行了同步監視器的wait()方法,wait是object的方法,範圍是該object實例所在的線程
  • 同步鎖:lock,更加強大的線程同步機制,通過顯式定義鎖對象來實現同步,也就是Lock對象,線程在訪問共享資源之前,需要先獲得鎖對象。線程安全控制中比較常用的是ReetrantLock可重入鎖。一個線程可以對已經加鎖的ReetrantLock再度加鎖。
class TestClass{
		private final ReentrantLock lock = new ReentrantLock();
		
		//需要定義線程安全的方法
		public void foo() {
			lock.lock();//加鎖
			try {
				// 需要保證線程安全的代碼
			}
			finally {
				lock.unlock();//使用finally塊保證釋放鎖
			}
		}
	}
  • 死鎖的問題:
    產生死鎖的四個必要條件:
    (1) 互斥條件:一個資源每次只能被一個進程使用。
    (2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
    (3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
    (4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。

    在系統中已經出現死鎖後,應該及時檢測到死鎖的發生,並採取適當的措施來解除死鎖。目前處理死鎖的方法可歸結爲以下四種:

    1. 預防死鎖:這是一種較簡單和直觀的事先預防的方法。方法是通過設置某些限制條件,去破壞產生死鎖的四個必要條件中的一個或者幾個,來預防發生死鎖。預防死鎖是一種較易實現的方法,已被廣泛使用。但是由於所施加的限制條件往往太嚴格,可能會導致系統資源利用率和系統吞吐量降低。

    2. 避免死鎖:該方法同樣是屬於事先預防的策略,但它並不須事先採取各種限制措施去破壞產生死鎖的的四個必要條件,而是在資源的動態分配過程中,用某種方法去防止系統進入不安全狀態,從而避免發生死鎖。

    3. 檢測死鎖:這種方法並不須事先採取任何限制性措施,也不必檢查系統是否已經進入不安全區,此方法允許系統在運行過程中發生死鎖。但可通過系統所設置的檢測機構,及時地檢測出死鎖的發生,並精確地確定與死鎖有關的進程和資源,然後採取適當措施,從系統中將已發生的死鎖清除掉。

    4. 解除死鎖:這是與檢測死鎖相配套的一種措施。當檢測到系統中已發生死鎖時,須將進程從死鎖狀態中解脫出來。常用的實施方法是撤銷或掛起一些進程,以便回收一些資源,再將這些資源分配給已處於阻塞狀態的進程,使之轉爲就緒狀態,以繼續運行。死鎖的檢測和解除措施,有可能使系統獲得較好的資源利用率和吞吐量,但在實現上難度也最大。

    java中應該避免死鎖的出現。

九、線程通信

  • Object類提供的wait(),notify(),notifyAll()三個方法,由同步監視器來調用,對於同步方法,其同步監視器是默認實例this,可以再同步方法中直接調用這三個方法。

    1. wait(): 當前線程等待或者等待若干ms,當前線程自動釋放同步監視器,線程進入等待狀態(阻塞),直到其他線程調用了該同步監視器的notify()或者notifyAll方法。
    2. notify():喚醒在同步監視器上等待的單個線程,若有多個線程等待,則任意選擇其中一個。
    3. notifyAll():喚醒在此同步監視器上等待的所有線程。
  • 使用Condition控制線程通信:使用Lock對象來保證同步問題時,我們可以使用Condition類來釋放Lock以及喚醒其他等待線程。

private final Lock lock = new ReentrantLock();
	// Condition實例綁定在一個Lock對象上
	private final Condition cond = lock.newCondition();
	
	public void Draw(double drawamount) {
		lock.lock();
		try {
			if(!flag)
				cond.await();//導致當前線程等待
			else {
				// ...
				cond.signalAll();// 喚醒其他線程
			}
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
		finally {
			lock.unlock();
		}
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章